Commit
Author: Kevin Schoon [me@kevinschoon.com]
Hash: 62f486cae572238cc47db4bd89719f77aebf542c
Timestamp: Sun, 31 Dec 2023 11:33:24 +0000 (1 year ago)

+257 -244 +/-5 browse
flatten out discussion pages
1diff --git a/src/web2/routes/discuss.rs b/src/web2/routes/discuss.rs
2index bf8b76c..c723d3b 100644
3--- a/src/web2/routes/discuss.rs
4+++ b/src/web2/routes/discuss.rs
5 @@ -1,10 +1,4 @@
6- use axum::{
7- debug_handler,
8- extract::{Extension, Path},
9- response::Html,
10- };
11- use serde::{Deserialize, Serialize};
12- use time::OffsetDateTime;
13+ use axum::{extract::Extension, response::Html};
14
15 use crate::config::Config;
16 use crate::web2::error::Error;
17 @@ -12,48 +6,6 @@ use crate::web2::middleware::rpc_initiator::{Initiator, Kind as InitiatorKind};
18 use crate::web2::middleware::template::Template;
19 use crate::web2::util;
20 use crate::web2::util::navigation;
21- use ayllu_api::{
22- mail_capnp::server::Client as MailClient, xmpp_capnp::server::Client as XmppClient,
23- };
24-
25- #[derive(Deserialize)]
26- pub struct Params {
27- pub address: String,
28- pub thread_id: Option<i64>,
29- }
30-
31- #[derive(Debug, Serialize, Default)]
32- struct Thread {
33- pub id: i64,
34- pub from: String,
35- pub subject: String,
36- pub n_replies: i64,
37- pub timestamp: String,
38- }
39-
40- #[derive(Debug, Serialize, Default)]
41- struct Message {
42- pub id: String,
43- pub created_at: String,
44- pub from_address: String,
45- pub body: String,
46- }
47-
48- #[derive(Debug, Serialize)]
49- struct ChannelWithStats {
50- pub name: String,
51- pub n_users: i64,
52- pub n_messages: i64,
53- pub online: bool,
54- }
55-
56- #[derive(Debug, Serialize)]
57- struct XmppMessage {
58- pub id: String,
59- pub nickname: String,
60- pub timestamp: String,
61- pub body: String,
62- }
63
64 pub async fn serve(
65 Extension(cfg): Extension<Config>,
66 @@ -78,189 +30,3 @@ pub async fn serve(
67 let body = templates.render("discuss.html", &ctx)?;
68 Ok(Html(body))
69 }
70-
71- pub mod xmpp {
72- use crate::web2::extractors::config::ConfigReader;
73-
74- use super::*;
75- pub async fn channels(
76- Extension(_cfg): Extension<Config>,
77- Extension((templates, mut ctx)): Extension<Template>,
78- Extension(initiator): Extension<Initiator>,
79- ) -> Result<Html<String>, Error> {
80- ctx.insert("title", "Discussions");
81- ctx.insert("nav_elements", &navigation::global("dicsuss", true));
82- ctx.insert("discnav", &util::navigation::discnav("xmpp"));
83- let xmpp_client = initiator.client(InitiatorKind::Xmpp).unwrap();
84- let channels = xmpp_client
85- .invoke(move |c: XmppClient| async move {
86- let mut channels: Vec<ChannelWithStats> = Vec::new();
87- let req = c.stats_request();
88- let stats = req.send().promise.await?;
89- for stat in stats.get()?.get_stats()? {
90- channels.push(ChannelWithStats {
91- name: stat.get_name()?.to_string().unwrap(),
92- n_users: stat.get_users_online(),
93- n_messages: stat.get_n_messages(),
94- online: true,
95- })
96- }
97- Ok(channels)
98- })
99- .await?;
100- ctx.insert("channels", &channels);
101- let body = templates.render("channels.html", &ctx)?;
102- Ok(Html(body))
103- }
104-
105- #[derive(Deserialize)]
106- pub struct ChannelParams {
107- pub channel: String,
108- pub last_message: Option<String>,
109- }
110-
111- pub async fn channel(
112- Path(params): Path<ChannelParams>,
113- Extension(_cfg): Extension<Config>,
114- ConfigReader(config): ConfigReader,
115- Extension((templates, mut ctx)): Extension<Template>,
116- Extension(initiator): Extension<Initiator>,
117- ) -> Result<Html<String>, Error> {
118- ctx.insert("title", "lists");
119- ctx.insert("nav_elements", &navigation::global("dicsuss", true));
120- ctx.insert("discnav", &util::navigation::discnav("xmpp"));
121- ctx.insert("channel", &params.channel);
122- let xmpp_client = initiator.client(InitiatorKind::Xmpp).unwrap();
123- let messages = xmpp_client
124- .invoke(move |c: XmppClient| async move {
125- let mut xmpp_messages: Vec<XmppMessage> = Vec::new();
126- let mut req = c.messages_request();
127- req.get().set_channel_name(params.channel.as_str().into());
128- req.get().set_limit(config.items_per_page as i64);
129- if let Some(id) = params.last_message { req.get().set_last_message(id.as_str().into()); }
130- let messages = req.send().promise.await?;
131- for message in messages.get()?.get_messages()? {
132- xmpp_messages.push(XmppMessage {
133- id: message.get_message_id()?.to_string().unwrap(),
134- nickname: message.get_nickname()?.to_string().unwrap(),
135- timestamp: message.get_timestamp()?.to_string().unwrap(),
136- body: message.get_body()?.to_string().unwrap(),
137- })
138- }
139- Ok(xmpp_messages)
140- })
141- .await?;
142- ctx.insert("messages", &messages);
143- // ctx.insert("lists", &cfg.mail.unwrap().lists);
144- let body = templates.render("channel.html", &ctx)?;
145- Ok(Html(body))
146- }
147- }
148-
149- pub mod mail {
150- use super::*;
151-
152- pub async fn lists(
153- Extension(cfg): Extension<Config>,
154- Extension((templates, mut ctx)): Extension<Template>,
155- ) -> Result<Html<String>, Error> {
156- ctx.insert("title", "lists");
157- ctx.insert("nav_elements", &navigation::global("dicsuss", true));
158- ctx.insert("discnav", &util::navigation::discnav("mail"));
159- // TODO: add stats method and display like xmpp
160- ctx.insert("lists", &cfg.mail.unwrap().lists.clone());
161- // ctx.insert("lists", &cfg.mail.unwrap().lists);
162- let body = templates.render("lists.html", &ctx)?;
163- Ok(Html(body))
164- }
165- #[debug_handler]
166- pub async fn threads(
167- Path(params): Path<Params>,
168- // Extension(db): Extension<Arc<Database>>,
169- Extension(initiator): Extension<Initiator>,
170- Extension(cfg): Extension<Config>,
171- Extension((templates, mut ctx)): Extension<Template>,
172- ) -> Result<Html<String>, Error> {
173- let lists = cfg.mail.unwrap().lists;
174- let list = match lists.iter().find(|list| list.address == params.address) {
175- Some(list) => Ok(list),
176- None => Err(Error::Message(format!(
177- "no list associated with: {}",
178- params.address
179- ))),
180- }?;
181- ctx.insert("title", &format!("list {}", list.address));
182- ctx.insert("nav_elements", &navigation::global("dicsuss", true));
183- ctx.insert("list", list);
184- ctx.insert("address", &params.address);
185- ctx.insert("discnav", &util::navigation::discnav("mail"));
186-
187- let mail_client = initiator.client(InitiatorKind::Mail).unwrap();
188- let threads = mail_client
189- .invoke(move |c: MailClient| async move {
190- let mut threads: Vec<Thread> = Vec::new();
191- let mut req = c.list_threads_request();
192- req.get().set_name(params.address.as_str().into());
193- let result = req.send().promise.await?;
194- let rpc_threads = result.get()?.get_threads()?;
195- for thread in rpc_threads.iter() {
196- let message = thread.get_first()?;
197- let timestamp =
198- OffsetDateTime::from_unix_timestamp(message.get_timestamp()).unwrap();
199- threads.push(Thread {
200- id: message.get_id(),
201- from: message.get_from()?.to_string().unwrap(),
202- subject: message.get_subject()?.to_string().unwrap(),
203- n_replies: thread.get_n_replies(),
204- timestamp: timestamp.to_string(),
205- })
206- }
207- Ok(threads)
208- })
209- .await?;
210- ctx.insert("threads", &threads);
211- let body = templates.render("threads.html", &ctx)?;
212- Ok(Html(body))
213- }
214-
215- pub async fn thread(
216- Path(params): Path<Params>,
217- Extension(initiator): Extension<Initiator>,
218- Extension(cfg): Extension<Config>,
219- Extension((templates, mut ctx)): Extension<Template>,
220- ) -> Result<Html<String>, Error> {
221- let lists = cfg.mail.unwrap().lists;
222- let list = match lists.iter().find(|list| list.address == params.address) {
223- Some(list) => Ok(list),
224- None => Err(Error::Message(format!(
225- "no list associated with: {}",
226- params.address
227- ))),
228- }?;
229- let mail_client = initiator.client(InitiatorKind::Mail).unwrap();
230- let messages = mail_client
231- .invoke(move |c: MailClient| async move {
232- let mut messages: Vec<Message> = Vec::new();
233- let mut req = c.read_thread_request();
234- req.get().set_id(params.thread_id.unwrap());
235- let result = req.send().promise.await?;
236- for message in result.get()?.get_thread()? {
237- messages.push(Message {
238- id: message.get_id().to_string(),
239- created_at: message.get_timestamp().to_string(), // TODO no epoch!
240- from_address: message.get_address()?.to_string().unwrap(),
241- body: message.get_body()?.to_string().unwrap(),
242- })
243- }
244- Ok(messages)
245- })
246- .await?;
247- ctx.insert("title", &format!("list {}", list.address));
248- ctx.insert("nav_elements", &navigation::global("dicsuss", true));
249- ctx.insert("discnav", &util::navigation::discnav("mail"));
250- ctx.insert("list", list);
251- ctx.insert("message", &messages);
252- let body = templates.render("thread.html", &ctx)?;
253- Ok(Html(body))
254- }
255- }
256 diff --git a/src/web2/routes/mail.rs b/src/web2/routes/mail.rs
257new file mode 100644
258index 0000000..e795a8f
259--- /dev/null
260+++ b/src/web2/routes/mail.rs
261 @@ -0,0 +1,142 @@
262+ use axum::{
263+ debug_handler,
264+ extract::{Extension, Path},
265+ response::Html,
266+ };
267+ use serde::{Deserialize, Serialize};
268+ use time::OffsetDateTime;
269+
270+ use crate::config::Config;
271+ use crate::web2::error::Error;
272+ use crate::web2::middleware::rpc_initiator::{Initiator, Kind as InitiatorKind};
273+ use crate::web2::middleware::template::Template;
274+ use crate::web2::util;
275+ use crate::web2::util::navigation;
276+ use ayllu_api::mail_capnp::server::Client as MailClient;
277+
278+ #[derive(Deserialize)]
279+ pub struct Params {
280+ pub address: String,
281+ pub thread_id: Option<i64>,
282+ }
283+
284+ #[derive(Debug, Serialize, Default)]
285+ struct Thread {
286+ pub id: i64,
287+ pub from: String,
288+ pub subject: String,
289+ pub n_replies: i64,
290+ pub timestamp: String,
291+ }
292+
293+ #[derive(Debug, Serialize, Default)]
294+ struct Message {
295+ pub id: String,
296+ pub created_at: String,
297+ pub from_address: String,
298+ pub body: String,
299+ }
300+
301+ pub async fn lists(
302+ Extension(cfg): Extension<Config>,
303+ Extension((templates, mut ctx)): Extension<Template>,
304+ ) -> Result<Html<String>, Error> {
305+ ctx.insert("title", "lists");
306+ ctx.insert("nav_elements", &navigation::global("dicsuss", true));
307+ ctx.insert("discnav", &util::navigation::discnav("mail"));
308+ // TODO: add stats method and display like xmpp
309+ ctx.insert("lists", &cfg.mail.unwrap().lists.clone());
310+ // ctx.insert("lists", &cfg.mail.unwrap().lists);
311+ let body = templates.render("lists.html", &ctx)?;
312+ Ok(Html(body))
313+ }
314+ #[debug_handler]
315+ pub async fn threads(
316+ Path(params): Path<Params>,
317+ // Extension(db): Extension<Arc<Database>>,
318+ Extension(initiator): Extension<Initiator>,
319+ Extension(cfg): Extension<Config>,
320+ Extension((templates, mut ctx)): Extension<Template>,
321+ ) -> Result<Html<String>, Error> {
322+ let lists = cfg.mail.unwrap().lists;
323+ let list = match lists.iter().find(|list| list.address == params.address) {
324+ Some(list) => Ok(list),
325+ None => Err(Error::Message(format!(
326+ "no list associated with: {}",
327+ params.address
328+ ))),
329+ }?;
330+ ctx.insert("title", &format!("list {}", list.address));
331+ ctx.insert("nav_elements", &navigation::global("dicsuss", true));
332+ ctx.insert("list", list);
333+ ctx.insert("address", &params.address);
334+ ctx.insert("discnav", &util::navigation::discnav("mail"));
335+
336+ let mail_client = initiator.client(InitiatorKind::Mail).unwrap();
337+ let threads = mail_client
338+ .invoke(move |c: MailClient| async move {
339+ let mut threads: Vec<Thread> = Vec::new();
340+ let mut req = c.list_threads_request();
341+ req.get().set_name(params.address.as_str().into());
342+ let result = req.send().promise.await?;
343+ let rpc_threads = result.get()?.get_threads()?;
344+ for thread in rpc_threads.iter() {
345+ let message = thread.get_first()?;
346+ let timestamp =
347+ OffsetDateTime::from_unix_timestamp(message.get_timestamp()).unwrap();
348+ threads.push(Thread {
349+ id: message.get_id(),
350+ from: message.get_from()?.to_string().unwrap(),
351+ subject: message.get_subject()?.to_string().unwrap(),
352+ n_replies: thread.get_n_replies(),
353+ timestamp: timestamp.to_string(),
354+ })
355+ }
356+ Ok(threads)
357+ })
358+ .await?;
359+ ctx.insert("threads", &threads);
360+ let body = templates.render("threads.html", &ctx)?;
361+ Ok(Html(body))
362+ }
363+
364+ pub async fn thread(
365+ Path(params): Path<Params>,
366+ Extension(initiator): Extension<Initiator>,
367+ Extension(cfg): Extension<Config>,
368+ Extension((templates, mut ctx)): Extension<Template>,
369+ ) -> Result<Html<String>, Error> {
370+ let lists = cfg.mail.unwrap().lists;
371+ let list = match lists.iter().find(|list| list.address == params.address) {
372+ Some(list) => Ok(list),
373+ None => Err(Error::Message(format!(
374+ "no list associated with: {}",
375+ params.address
376+ ))),
377+ }?;
378+ let mail_client = initiator.client(InitiatorKind::Mail).unwrap();
379+ let messages = mail_client
380+ .invoke(move |c: MailClient| async move {
381+ let mut messages: Vec<Message> = Vec::new();
382+ let mut req = c.read_thread_request();
383+ req.get().set_id(params.thread_id.unwrap());
384+ let result = req.send().promise.await?;
385+ for message in result.get()?.get_thread()? {
386+ messages.push(Message {
387+ id: message.get_id().to_string(),
388+ created_at: message.get_timestamp().to_string(), // TODO no epoch!
389+ from_address: message.get_address()?.to_string().unwrap(),
390+ body: message.get_body()?.to_string().unwrap(),
391+ })
392+ }
393+ Ok(messages)
394+ })
395+ .await?;
396+ ctx.insert("title", &format!("list {}", list.address));
397+ ctx.insert("nav_elements", &navigation::global("dicsuss", true));
398+ ctx.insert("discnav", &util::navigation::discnav("mail"));
399+ ctx.insert("list", list);
400+ ctx.insert("message", &messages);
401+ let body = templates.render("thread.html", &ctx)?;
402+ Ok(Html(body))
403+ }
404 diff --git a/src/web2/routes/mod.rs b/src/web2/routes/mod.rs
405index 66a1d19..b3b25fc 100644
406--- a/src/web2/routes/mod.rs
407+++ b/src/web2/routes/mod.rs
408 @@ -12,7 +12,9 @@ pub mod discuss;
409 pub mod finger;
410 pub mod index;
411 pub mod log;
412+ pub mod mail;
413 pub mod refs;
414 pub mod repo;
415 pub mod robots;
416 pub mod rss;
417+ pub mod xmpp;
418 diff --git a/src/web2/routes/xmpp.rs b/src/web2/routes/xmpp.rs
419new file mode 100644
420index 0000000..3187057
421--- /dev/null
422+++ b/src/web2/routes/xmpp.rs
423 @@ -0,0 +1,104 @@
424+ use axum::{
425+ extract::{Extension, Path},
426+ response::Html,
427+ };
428+ use serde::{Deserialize, Serialize};
429+
430+ use crate::config::Config;
431+ use crate::web2::error::Error;
432+ use crate::web2::extractors::config::ConfigReader;
433+ use crate::web2::middleware::rpc_initiator::{Initiator, Kind as InitiatorKind};
434+ use crate::web2::middleware::template::Template;
435+ use crate::web2::util;
436+ use crate::web2::util::navigation;
437+ use ayllu_api::xmpp_capnp::server::Client as XmppClient;
438+
439+ #[derive(Debug, Serialize)]
440+ struct ChannelWithStats {
441+ pub name: String,
442+ pub n_users: i64,
443+ pub n_messages: i64,
444+ pub online: bool,
445+ }
446+
447+ #[derive(Debug, Serialize)]
448+ struct XmppMessage {
449+ pub id: String,
450+ pub nickname: String,
451+ pub timestamp: String,
452+ pub body: String,
453+ }
454+ pub async fn channels(
455+ Extension(_cfg): Extension<Config>,
456+ Extension((templates, mut ctx)): Extension<Template>,
457+ Extension(initiator): Extension<Initiator>,
458+ ) -> Result<Html<String>, Error> {
459+ ctx.insert("title", "Discussions");
460+ ctx.insert("nav_elements", &navigation::global("dicsuss", true));
461+ ctx.insert("discnav", &util::navigation::discnav("xmpp"));
462+ let xmpp_client = initiator.client(InitiatorKind::Xmpp).unwrap();
463+ let channels = xmpp_client
464+ .invoke(move |c: XmppClient| async move {
465+ let mut channels: Vec<ChannelWithStats> = Vec::new();
466+ let req = c.stats_request();
467+ let stats = req.send().promise.await?;
468+ for stat in stats.get()?.get_stats()? {
469+ channels.push(ChannelWithStats {
470+ name: stat.get_name()?.to_string().unwrap(),
471+ n_users: stat.get_users_online(),
472+ n_messages: stat.get_n_messages(),
473+ online: true,
474+ })
475+ }
476+ Ok(channels)
477+ })
478+ .await?;
479+ ctx.insert("channels", &channels);
480+ let body = templates.render("channels.html", &ctx)?;
481+ Ok(Html(body))
482+ }
483+
484+ #[derive(Deserialize)]
485+ pub struct ChannelParams {
486+ pub channel: String,
487+ pub last_message: Option<String>,
488+ }
489+
490+ pub async fn channel(
491+ Path(params): Path<ChannelParams>,
492+ Extension(_cfg): Extension<Config>,
493+ ConfigReader(config): ConfigReader,
494+ Extension((templates, mut ctx)): Extension<Template>,
495+ Extension(initiator): Extension<Initiator>,
496+ ) -> Result<Html<String>, Error> {
497+ ctx.insert("title", "lists");
498+ ctx.insert("nav_elements", &navigation::global("dicsuss", true));
499+ ctx.insert("discnav", &util::navigation::discnav("xmpp"));
500+ ctx.insert("channel", &params.channel);
501+ let xmpp_client = initiator.client(InitiatorKind::Xmpp).unwrap();
502+ let messages = xmpp_client
503+ .invoke(move |c: XmppClient| async move {
504+ let mut xmpp_messages: Vec<XmppMessage> = Vec::new();
505+ let mut req = c.messages_request();
506+ req.get().set_channel_name(params.channel.as_str().into());
507+ req.get().set_limit(config.items_per_page as i64);
508+ if let Some(id) = params.last_message {
509+ req.get().set_last_message(id.as_str().into());
510+ }
511+ let messages = req.send().promise.await?;
512+ for message in messages.get()?.get_messages()? {
513+ xmpp_messages.push(XmppMessage {
514+ id: message.get_message_id()?.to_string().unwrap(),
515+ nickname: message.get_nickname()?.to_string().unwrap(),
516+ timestamp: message.get_timestamp()?.to_string().unwrap(),
517+ body: message.get_body()?.to_string().unwrap(),
518+ })
519+ }
520+ Ok(xmpp_messages)
521+ })
522+ .await?;
523+ ctx.insert("messages", &messages);
524+ // ctx.insert("lists", &cfg.mail.unwrap().lists);
525+ let body = templates.render("channel.html", &ctx)?;
526+ Ok(Html(body))
527+ }
528 diff --git a/src/web2/server.rs b/src/web2/server.rs
529index e542d24..bb597ad 100644
530--- a/src/web2/server.rs
531+++ b/src/web2/server.rs
532 @@ -34,10 +34,12 @@ use crate::web2::routes::discuss;
533 use crate::web2::routes::finger;
534 use crate::web2::routes::index;
535 use crate::web2::routes::log as log_route;
536+ use crate::web2::routes::mail;
537 use crate::web2::routes::refs;
538 use crate::web2::routes::repo;
539 use crate::web2::routes::robots;
540 use crate::web2::routes::rss;
541+ use crate::web2::routes::xmpp;
542 use crate::web2::terautil;
543 use ayllu_database::Wrapper as Database;
544
545 @@ -183,9 +185,9 @@ pub async fn serve(cfg: &Config) -> Result<(), Box<dyn Error>> {
546 .nest(
547 "/mail",
548 Router::new()
549- .route("/", routing::get(discuss::mail::lists))
550- .route("/:address", routing::get(discuss::mail::threads))
551- .route("/:address/:thread_id", routing::get(discuss::mail::thread))
552+ .route("/", routing::get(mail::lists))
553+ .route("/:address", routing::get(mail::threads))
554+ .route("/:address/:thread_id", routing::get(mail::thread))
555 .layer(from_fn_with_state(
556 Arc::new((cfg.clone(), templates.clone(), mail_required_plugins)),
557 rpc_initiator::required,
558 @@ -198,12 +200,9 @@ pub async fn serve(cfg: &Config) -> Result<(), Box<dyn Error>> {
559 .nest(
560 "/xmpp",
561 Router::new()
562- .route("/", routing::get(discuss::xmpp::channels))
563- .route("/:channel", routing::get(discuss::xmpp::channel))
564- .route(
565- "/:channel/:last_message",
566- routing::get(discuss::xmpp::channel),
567- )
568+ .route("/", routing::get(xmpp::channels))
569+ .route("/:channel", routing::get(xmpp::channel))
570+ .route("/:channel/:last_message", routing::get(xmpp::channel))
571 .layer(from_fn_with_state(
572 Arc::new((cfg.clone(), templates.clone(), xmpp_required_plugins)),
573 rpc_initiator::required,