headless_talk/channel/
mod.rs

1pub mod normal;
2pub mod open;
3
4use std::ops::Bound;
5
6use crate::{
7    conn::Conn,
8    database::{
9        model::{channel::ChannelListRow, chat::ChatRow},
10        schema::{chat, user_profile},
11        DatabasePool, PoolTaskError,
12    },
13    user::{DisplayUser, DisplayUserProfile},
14    ClientResult,
15};
16use arrayvec::ArrayVec;
17use diesel::{
18    dsl::sql, sql_types::Integer, BoolExpressionMethods, ExpressionMethods, OptionalExtension,
19    QueryDsl, RunQueryDsl,
20};
21use nohash_hasher::IntMap;
22use serde::Deserialize;
23use talk_loco_client::talk::{
24    channel::{ChannelMeta, ChannelType},
25    chat::{Chat, ChatType, Chatlog},
26    session::{channel::write, TalkSession},
27};
28
29use self::{normal::NormalChannel, open::OpenChannel};
30
31pub type ChannelMetaMap = IntMap<i32, ChannelMeta>;
32
33pub type UserList<T> = Vec<(i64, T)>;
34
35#[derive(Debug, Clone)]
36pub struct ListPreviewChat {
37    pub user: Option<DisplayUser>,
38    pub chatlog: Chatlog,
39}
40
41#[derive(Debug, Clone)]
42pub struct ListChannelProfile {
43    pub name: String,
44    pub image: Option<ListChannelProfileImage>,
45}
46
47#[derive(Debug, Clone, Deserialize)]
48#[serde(rename_all = "camelCase")]
49pub struct ListChannelProfileImage {
50    pub image_url: String,
51    pub full_image_url: String,
52}
53
54#[derive(Debug, Clone)]
55pub struct ChannelListItem {
56    pub channel_type: ChannelType,
57
58    pub last_chat: Option<ListPreviewChat>,
59
60    pub display_users: ArrayVec<DisplayUser, 4>,
61
62    pub unread_count: i32,
63
64    pub active_user_count: i32,
65
66    pub profile: ListChannelProfile,
67}
68
69#[derive(Debug, Clone)]
70pub enum ClientChannel {
71    Normal(NormalChannel),
72    Open(OpenChannel),
73}
74
75#[derive(Debug, Clone, Copy)]
76pub struct ChannelOp<'a> {
77    id: i64,
78    conn: &'a Conn,
79}
80
81impl<'a> ChannelOp<'a> {
82    pub(crate) const fn new(id: i64, conn: &'a Conn) -> Self {
83        Self { id, conn }
84    }
85
86    pub async fn send_chat(self, chat: Chat, no_seen: bool) -> ClientResult<Chatlog> {
87        let res = TalkSession(&self.conn.session)
88            .channel(self.id)
89            .write_chat(&write::Request {
90                chat_type: chat.chat_type.0,
91                msg_id: chat.message_id,
92                message: chat.content.message.as_deref(),
93                no_seen,
94                attachment: chat.content.attachment.as_deref(),
95                supplement: chat.content.supplement.as_deref(),
96            })
97            .await?;
98
99        let logged = res.chatlog.unwrap_or(Chatlog {
100            channel_id: self.id,
101
102            log_id: res.log_id,
103            prev_log_id: Some(res.prev_id),
104
105            author_id: self.conn.user_id,
106
107            send_at: res.send_at,
108
109            chat,
110
111            referer: None,
112        });
113
114        self.conn
115            .pool
116            .spawn({
117                let logged = logged.clone();
118
119                move |conn| {
120                    diesel::replace_into(chat::table)
121                        .values(ChatRow::from_chatlog(logged, None))
122                        .execute(conn)?;
123
124                    Ok(())
125                }
126            })
127            .await?;
128
129        Ok(logged)
130    }
131
132    pub async fn delete_chat(self, log_id: i64) -> ClientResult<()> {
133        let id = self.id;
134
135        TalkSession(&self.conn.session)
136            .channel(id)
137            .delete_chat(log_id)
138            .await?;
139
140        self.conn
141            .pool
142            .spawn(move |conn| {
143                diesel::update(chat::table)
144                    .filter(chat::channel_id.eq(id).and(chat::log_id.eq(log_id)))
145                    .set(
146                        chat::type_.eq(sql("(type | ")
147                            .bind::<Integer, _>(ChatType::DELETED_MASK)
148                            .sql(")")),
149                    )
150                    .execute(conn)?;
151
152                Ok(())
153            })
154            .await?;
155
156        Ok(())
157    }
158
159    pub async fn delete_chat_local(
160        self,
161        id: i64,
162        log_id: i64,
163        deleted_time: i64,
164    ) -> Result<bool, PoolTaskError> {
165        self.conn
166            .pool
167            .spawn(move |conn| {
168                let count = diesel::update(chat::table)
169                    .filter(chat::channel_id.eq(id).and(chat::log_id.eq(log_id)))
170                    .set(chat::deleted_time.eq(deleted_time))
171                    .execute(conn)?;
172
173                Ok(count > 0)
174            })
175            .await
176    }
177
178    pub async fn load_chat_from(
179        self,
180        log_id: Bound<i64>,
181        count: i64,
182    ) -> Result<Vec<Chatlog>, PoolTaskError> {
183        let id = self.id;
184
185        self.conn
186            .pool
187            .spawn(move |conn| {
188                let iter = match log_id {
189                    Bound::Included(log_id) => chat::table
190                        .filter(chat::channel_id.eq(id).and(chat::log_id.le(log_id)))
191                        .order_by(chat::log_id.desc())
192                        .limit(count)
193                        .load_iter::<ChatRow, _>(conn),
194                    Bound::Excluded(log_id) => chat::table
195                        .filter(chat::channel_id.eq(id).and(chat::log_id.lt(log_id)))
196                        .order_by(chat::log_id.desc())
197                        .limit(count)
198                        .load_iter::<ChatRow, _>(conn),
199                    Bound::Unbounded => chat::table
200                        .filter(chat::channel_id.eq(id))
201                        .order_by(chat::log_id.desc())
202                        .limit(count)
203                        .load_iter::<ChatRow, _>(conn),
204                }?;
205
206                Ok(iter
207                    .map(|row| row.map(|row| row.into()))
208                    .collect::<Result<_, _>>()?)
209            })
210            .await
211    }
212}
213
214pub(crate) async fn load_list_item(
215    pool: &DatabasePool,
216    row: &ChannelListRow,
217) -> Result<Option<ChannelListItem>, PoolTaskError> {
218    let channel_type = ChannelType::from(row.channel_type.as_str());
219
220    let (last_chat, display_users) = pool
221        .spawn_transaction({
222            let channel_id = row.id;
223            let display_user_id_list =
224                serde_json::from_str::<ArrayVec<i64, 4>>(&row.display_users).unwrap_or_default();
225
226            move |conn| {
227                let last_chat: Option<Chatlog> = chat::table
228                    .filter(
229                        chat::channel_id
230                            .eq(channel_id)
231                            .and(chat::deleted_time.is_null()),
232                    )
233                    .order(chat::log_id.desc())
234                    .select(chat::all_columns)
235                    .first::<ChatRow>(conn)
236                    .optional()?
237                    .map(Into::into);
238
239                let last_chat: Option<ListPreviewChat> = if let Some(chat) = last_chat {
240                    let user = if let Some((nickname, image_url)) = user_profile::table
241                        .select((user_profile::nickname, user_profile::profile_url))
242                        .filter(
243                            user_profile::channel_id
244                                .eq(channel_id)
245                                .and(user_profile::id.eq(chat.author_id)),
246                        )
247                        .first::<(String, String)>(conn)
248                        .optional()?
249                    {
250                        Some(DisplayUser {
251                            id: chat.author_id,
252                            profile: DisplayUserProfile {
253                                nickname,
254                                image_url: Some(image_url),
255                            },
256                        })
257                    } else {
258                        None
259                    };
260
261                    Some(ListPreviewChat {
262                        user,
263                        chatlog: chat,
264                    })
265                } else {
266                    None
267                };
268
269                let mut display_users = ArrayVec::<DisplayUser, 4>::new();
270
271                for id in display_user_id_list {
272                    if let Some((nickname, profile_url)) = user_profile::table
273                        .select((user_profile::nickname, user_profile::profile_url))
274                        .filter(
275                            user_profile::channel_id
276                                .eq(channel_id)
277                                .and(user_profile::id.eq(id)),
278                        )
279                        .first::<(String, String)>(conn)
280                        .optional()?
281                    {
282                        display_users.push(DisplayUser {
283                            id,
284                            profile: DisplayUserProfile {
285                                nickname,
286                                image_url: Some(profile_url),
287                            },
288                        });
289                    }
290                }
291
292                Ok((last_chat, display_users))
293            }
294        })
295        .await?;
296
297    let profile = match channel_type {
298        ChannelType::DirectChat | ChannelType::MultiChat => {
299            normal::load_list_profile(pool, &display_users, row).await?
300        }
301
302        _ => return Ok(None),
303    };
304
305    Ok(Some(ChannelListItem {
306        channel_type,
307        last_chat: last_chat.map(Into::into),
308        display_users,
309        unread_count: row.unread_count,
310        active_user_count: row.active_user_count,
311        profile,
312    }))
313}