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}