Commit

Author:

Hash:

Timestamp:

+206 -355 +/-22 browse

Kevin Schoon [me@kevinschoon.com]

731ff4d7e2d40e056362047d8619e9d81b19238d

Tue, 06 Feb 2024 22:45:43 +0000 (1.3 years ago)

appease all clippy wisdom
1diff --git a/ayllu-build/src/evaluate.rs b/ayllu-build/src/evaluate.rs
2index 54bb5e6..644b4d1 100644
3--- a/ayllu-build/src/evaluate.rs
4+++ b/ayllu-build/src/evaluate.rs
5 @@ -334,10 +334,14 @@ impl Runtime {
6 temp_dir: self.log_dir.clone(),
7 tee_output: self.tee_output,
8 };
9- let mut ctx = Context::default();
10+
11 // TODO: more context
12- ctx.manifest_id = manifest_id;
13- ctx.workflow_id = state.current_workflow().unwrap().0;
14+ let ctx = Context {
15+ manifest_id,
16+ workflow_id: state.current_workflow().unwrap().0,
17+ ..Default::default()
18+ };
19+
20 let (stdout, stderr, exit_code) = executor.execute(step, ctx)?;
21 if !exit_code.success() {
22 warn!("step {} has failed: {:?}", step.name, exit_code);
23 @@ -355,13 +359,10 @@ impl Runtime {
24 }
25 }
26 // clean up last workflow
27- match state.current_workflow() {
28- Some((id, duration)) => {
29- db.update_workflow_finish(id, duration).await?;
30- }
31-
32- None => {}
33+ if let Some((id, duration)) = state.current_workflow() {
34+ db.update_workflow_finish(id, duration).await?;
35 }
36+
37 db.update_manifest_finish(manifest_id, state.runtime())
38 .await?;
39 Ok(())
40 diff --git a/ayllu-build/src/rpc_server.rs b/ayllu-build/src/rpc_server.rs
41index fe57e0b..10fe521 100644
42--- a/ayllu-build/src/rpc_server.rs
43+++ b/ayllu-build/src/rpc_server.rs
44 @@ -1,11 +1,7 @@
45-
46-
47 use anyhow::Result;
48 use capnp::{capability::Promise, Error as CapnpError};
49- use sysinfo::SystemExt;
50
51 use crate::config::Builder as Config;
52- // use crate::database_ext::XmppExt;
53 use ayllu_api::build_capnp::worker::{
54 Client, ListManifestsParams, ListManifestsResults, ReadManifestParams, ReadManifestResults,
55 Server, SubmitParams, SubmitResults,
56 @@ -14,6 +10,7 @@ use ayllu_api::build_capnp::worker::{
57 use ayllu_database::{Builder, Wrapper as Database};
58 use ayllu_rpc::Server as RpcHelper;
59
60+ #[allow(dead_code)]
61 struct ServerImpl {
62 db: Database,
63 }
64 diff --git a/ayllu-xmpp/src/rpc_server.rs b/ayllu-xmpp/src/rpc_server.rs
65index b6fb493..122bff0 100644
66--- a/ayllu-xmpp/src/rpc_server.rs
67+++ b/ayllu-xmpp/src/rpc_server.rs
68 @@ -9,7 +9,7 @@ use crate::database_ext::XmppExt;
69 use ayllu_api::xmpp_capnp::server::{
70 Client, MessagesParams, MessagesResults, Server, StatsParams, StatsResults,
71 };
72- use ayllu_database::Wrapper as Database;
73+ use ayllu_database::{Wrapper as Database, Builder};
74 use ayllu_rpc::Server as RpcHelper;
75
76 struct ServerImpl {
77 @@ -93,7 +93,7 @@ impl Server for ServerImpl {
78 }
79
80 pub async fn serve(cfg: Config) -> Result<(), Box<dyn StdError>> {
81- let db = Database::new(&cfg.database.path, true, false).await?;
82+ let db = Builder::default().url(&cfg.database.path).build().await?;
83 let server = ServerImpl { db };
84 let runtime = RpcHelper::<Client, ServerImpl>::new(&cfg.socket_path, server);
85 runtime.serve().await?;
86 diff --git a/ayllu-xmpp/src/xmpp_bot.rs b/ayllu-xmpp/src/xmpp_bot.rs
87index 33ec620..d25206b 100644
88--- a/ayllu-xmpp/src/xmpp_bot.rs
89+++ b/ayllu-xmpp/src/xmpp_bot.rs
90 @@ -8,12 +8,15 @@ use xmpp::{ClientBuilder, ClientFeature, ClientType, Event};
91
92 use crate::config::Xmpp as Config;
93 use crate::database_ext::XmppExt;
94- use ayllu_database::Wrapper as Database;
95+ use ayllu_database::Builder;
96
97 pub type Error = Box<dyn StdError>;
98
99 pub async fn serve(config: Config) -> Result<(), Error> {
100- let db = Database::new(&config.database.path, false, false).await?;
101+ let db = Builder::default()
102+ .url(&config.database.path)
103+ .build()
104+ .await?;
105 let jid = BareJid::new(&config.jid)?;
106 let mut client = ClientBuilder::new(jid, &config.password)
107 .set_client(ClientType::Bot, "ayllu-xmpp")
108 @@ -54,10 +57,7 @@ pub async fn serve(config: Config) -> Result<(), Error> {
109 .unwrap()
110 .clone();
111 client
112- .request_room_history(
113- channel_jid.clone(),
114- last_message_id.as_deref(),
115- )
116+ .request_room_history(channel_jid.clone(), last_message_id.as_deref())
117 .await
118 }
119 }
120 diff --git a/crates/database/src/lib.rs b/crates/database/src/lib.rs
121index f19b1a6..1c38990 100644
122--- a/crates/database/src/lib.rs
123+++ b/crates/database/src/lib.rs
124 @@ -92,15 +92,12 @@ impl Builder {
125 opts = opts.locking_mode(SqliteLockingMode::Normal)
126 }
127 let pool = pool_opts.connect_with(opts).await?;
128- match self.migrations {
129- Some(migrations_path) => {
130- log::info!("running migrations");
131- Migrator::new(Path::new(&migrations_path))
132- .await?
133- .run(&pool)
134- .await?
135- }
136- None => {}
137+ if let Some(migrations_path) = self.migrations {
138+ log::info!("running migrations");
139+ Migrator::new(Path::new(&migrations_path))
140+ .await?
141+ .run(&pool)
142+ .await?
143 }
144 Ok(Wrapper { pool })
145 }
146 @@ -112,30 +109,6 @@ pub struct Wrapper {
147 }
148
149 impl Wrapper {
150- #[deprecated]
151- pub async fn new(url: &str, read_only: bool, log_queries: bool) -> Result<Self, Error> {
152- let log_level = if log_queries {
153- // don't log queries when the global logger is at level INFO
154- log::LevelFilter::Info
155- } else {
156- log::LevelFilter::Debug
157- };
158- // TODO: set read-only for main web serving mode
159- let mut opts = SqliteConnectOptions::new()
160- .log_statements(log_level)
161- .filename(url)
162- .serialized(true)
163- // .read_only(read_only)
164- .create_if_missing(false);
165- if read_only {
166- opts = opts.read_only(true)
167- } else {
168- opts = opts.locking_mode(SqliteLockingMode::Normal)
169- }
170- let pool = SqlitePool::connect_with(opts).await?;
171- Ok(Wrapper { pool })
172- }
173-
174 pub async fn close(&self) -> Result<(), Error> {
175 self.pool.close().await;
176 Ok(())
177 diff --git a/crates/git/src/clone.rs b/crates/git/src/clone.rs
178index 69c5f43..0af2c86 100644
179--- a/crates/git/src/clone.rs
180+++ b/crates/git/src/clone.rs
181 @@ -1,6 +1,7 @@
182 use crate::error::Error;
183 /// clone a remote repository into the given path with the specified depth.
184 /// this is only used as a part of ayllu-build
185+ #[allow(dead_code)]
186 pub fn clone(_url: &str, _path: &str, _depth: Option<i64>) -> Result<(), Error> {
187- Ok(())
188+ todo!()
189 }
190 diff --git a/crates/git/src/wrapper.rs b/crates/git/src/wrapper.rs
191index 71a2ae8..9af7620 100644
192--- a/crates/git/src/wrapper.rs
193+++ b/crates/git/src/wrapper.rs
194 @@ -42,15 +42,6 @@ impl Wrapper {
195 })
196 }
197
198- pub fn from_str(path: &str) -> Result<Self, Error> {
199- let path = Path::new(path);
200- let repository = Repository::open(path)?;
201- Ok(Wrapper {
202- path: path.to_str().unwrap().to_string(),
203- repository: Box::new(repository),
204- })
205- }
206-
207 pub fn path(&self) -> String {
208 self.path.clone()
209 }
210 diff --git a/crates/rpc/src/lib.rs b/crates/rpc/src/lib.rs
211index 15790ba..618b648 100644
212--- a/crates/rpc/src/lib.rs
213+++ b/crates/rpc/src/lib.rs
214 @@ -22,8 +22,6 @@ pub enum Kind {
215 Mail,
216 }
217
218- // pub type Error = Box<dyn StdError>;
219-
220 #[derive(Debug)]
221 pub enum Error {
222 CapnpError(CapnpError),
223 @@ -81,11 +79,8 @@ where
224
225 pub async fn serve(self) -> Result<(), Error> {
226 let socket_path = Path::new(&self.socket_path);
227- match metadata(socket_path) {
228- Ok(_) => {
229- remove_file(socket_path)?;
230- }
231- Err(_) => {}
232+ if metadata(socket_path).is_ok() {
233+ remove_file(socket_path)?;
234 };
235 // TODO: need to support both sockets and TCP connections for build
236 // servers since they will cross network boundaries on QEMU, etc.
237 @@ -158,6 +153,7 @@ pub mod helpers {
238 }
239
240 /// service for testing only
241+ #[allow(dead_code)]
242 mod hello_capnp {
243 include!(concat!(env!("OUT_DIR"), "/hello_capnp.rs"));
244 }
245 diff --git a/crates/scheduler/src/lib.rs b/crates/scheduler/src/lib.rs
246index 2dde645..399e376 100644
247--- a/crates/scheduler/src/lib.rs
248+++ b/crates/scheduler/src/lib.rs
249 @@ -91,24 +91,21 @@ where
250 spawn(move || {
251 let mut position = 0;
252 loop {
253- match rx.recv_timeout(Duration::from_millis(TIMEOUT_MS)) {
254- Ok(next_job) => {
255- info!("sending job to worker: {}", position);
256- let worker = workers.get(position).unwrap();
257- worker.send(next_job).unwrap();
258- if position + 1 == n_workers {
259- position = 0;
260- } else {
261- position += 1;
262- }
263+ if let Ok(next_job) = rx.recv_timeout(Duration::from_millis(TIMEOUT_MS)) {
264+ info!("sending job to worker: {}", position);
265+ let worker = workers.get(position).unwrap();
266+ worker.send(next_job).unwrap();
267+ if position + 1 == n_workers {
268+ position = 0;
269+ } else {
270+ position += 1;
271 }
272- Err(_) => {}
273 }
274- match rx_shutdown.recv_timeout(Duration::from_millis(TIMEOUT_MS)) {
275- Ok(_) => {
276- info!("scheduler is shutting down")
277- }
278- Err(_) => {}
279+ if rx_shutdown
280+ .recv_timeout(Duration::from_millis(TIMEOUT_MS))
281+ .is_ok()
282+ {
283+ info!("scheduler is shutting down")
284 }
285 }
286 });
287 diff --git a/src/job_server/client.rs b/src/job_server/client.rs
288deleted file mode 100644
289index 64e9e4a..0000000
290--- a/src/job_server/client.rs
291+++ /dev/null
292 @@ -1,73 +0,0 @@
293- use ayllu_api::jobs_capnp::server::Client;
294- use ayllu_api::models::{Job, Kind};
295-
296-
297- use capnp::Error;
298- use capnp_rpc::{rpc_twoparty_capnp::Side, twoparty, RpcSystem};
299- use tokio::net::UnixStream;
300- use tokio_util::compat::TokioAsyncReadCompatExt;
301-
302- use futures::AsyncReadExt;
303-
304- pub struct Jobs {
305- socket_path: String,
306- }
307-
308- impl Jobs {
309- pub fn new(path: &str) -> Self {
310- Jobs {
311- socket_path: path.to_string(),
312- }
313- }
314-
315- async fn _make_client(&self) -> Result<Client, Error> {
316- let stream = UnixStream::connect(self.socket_path.as_str()).await?;
317- let (reader, writer) = TokioAsyncReadCompatExt::compat(stream).split();
318- let rpc_network = Box::new(twoparty::VatNetwork::new(
319- reader,
320- writer,
321- Side::Client,
322- Default::default(),
323- ));
324- let mut rpc_system = RpcSystem::new(rpc_network, None);
325- let jobs_client: Client = rpc_system.bootstrap(Side::Server);
326- tokio::task::spawn_local(rpc_system);
327- Ok(jobs_client)
328- }
329-
330- pub async fn submit(&self, repo_path: &str, kind: Kind) -> Result<(), Error> {
331- let client = self._make_client().await?;
332- let mut request = client.submit_request();
333- request.get().set_repo_path(repo_path.into());
334- request.get().set_kind(kind.into());
335- request.send().promise.await?;
336- Ok(())
337- }
338-
339- pub async fn list(&self) -> Result<Vec<Job>, Error> {
340- let client = self._make_client().await?;
341- let result = client.list_request().send().promise.await?;
342- let jobs: Vec<Job> = result
343- .get()?
344- .get_jobs()?
345- .iter()
346- .map(|item| Job {
347- id: item.get_id(),
348- repo_path: String::from_utf8(item.get_repo_path().unwrap().0.to_vec()).unwrap(),
349- created_at: item.get_created_at(),
350- runtime: item.get_runtime(),
351- kind: Kind::from(item.get_kind().unwrap()),
352- commits: item.get_commits(),
353- })
354- .collect();
355- Ok(jobs)
356- }
357-
358- pub async fn purge(&self, repo_path: &str) -> Result<(), Error> {
359- let client = self._make_client().await?;
360- let mut request = client.purge_request();
361- request.get().set_repo_path(repo_path.into());
362- request.send().promise.await?;
363- Ok(())
364- }
365- }
366 diff --git a/src/job_server/mod.rs b/src/job_server/mod.rs
367index 417f7d4..0cceca5 100644
368--- a/src/job_server/mod.rs
369+++ b/src/job_server/mod.rs
370 @@ -1,7 +1,6 @@
371 pub use commands::*;
372 pub use server::serve;
373
374- mod client;
375 mod commands;
376 mod jobs;
377 mod runner;
378 diff --git a/src/job_server/server.rs b/src/job_server/server.rs
379index cfdec3b..c66afcb 100644
380--- a/src/job_server/server.rs
381+++ b/src/job_server/server.rs
382 @@ -1,6 +1,5 @@
383 use std::error::Error as StdError;
384 use std::sync::Arc;
385- use std::sync::OnceLock;
386
387 use capnp::{capability::Promise, Error};
388 use capnp_rpc::pry;
389 @@ -12,10 +11,9 @@ use ayllu_api::jobs_capnp::server::{
390 Client, ListParams, ListResults, PurgeParams, PurgeResults, Server, SubmitParams, SubmitResults,
391 };
392 use ayllu_api::models::{Job, Kind};
393- use ayllu_database::Wrapper as Database;
394+ use ayllu_database::{Wrapper as Database, Builder};
395 use ayllu_rpc::Server as CapnpServerHelper;
396
397- static THREAD_CONFIG: OnceLock<Config> = OnceLock::new();
398
399 #[derive(Clone)]
400 pub struct ServerImpl {
401 @@ -107,7 +105,7 @@ impl Server for ServerImpl {
402 }
403
404 pub async fn serve(cfg: &Config) -> Result<(), Box<dyn StdError>> {
405- let db = Database::new(&cfg.database.path, false, false).await?;
406+ let db = Builder::default().url(&cfg.database.path).build().await?;
407 let server = ServerImpl { db: Arc::new(db) };
408 let runtime = CapnpServerHelper::<Client, ServerImpl>::new(&cfg.jobs_socket_path, server);
409 runtime.serve().await.map_err(|e| e.into())
410 diff --git a/src/languages.rs b/src/languages.rs
411index 1ddd244..9b236c8 100644
412--- a/src/languages.rs
413+++ b/src/languages.rs
414 @@ -54,6 +54,7 @@ struct Language {
415 pub extensions: Option<Vec<String>>,
416 pub filenames: Option<Vec<String>>,
417 pub aliases: Option<Vec<String>>,
418+ #[allow(dead_code)]
419 #[serde(rename = "codemirror_mime_type")]
420 pub mime_type: Option<String>,
421 }
422 @@ -174,8 +175,8 @@ impl Languages {
423 let mut split = line.split(' ');
424 let first = split.nth(0).unwrap();
425 let path = Path::new(first.trim_start_matches("#!"));
426- match path.file_name() {
427- Some(name) => match name.to_str().unwrap() {
428+ if let Some(name) = path.file_name() {
429+ match name.to_str().unwrap() {
430 "env" => {
431 // special case for env shebang where the input
432 // uses an -S arg, see man env.1
433 @@ -201,8 +202,7 @@ impl Languages {
434 }
435 }
436 name => return Some(Hint(name.to_string())),
437- },
438- None => {}
439+ }
440 }
441 }
442 }
443 diff --git a/src/web2/highlight.rs b/src/web2/highlight.rs
444index 5dada46..462c804 100644
445--- a/src/web2/highlight.rs
446+++ b/src/web2/highlight.rs
447 @@ -9,9 +9,9 @@ use std::sync::RwLock;
448 use comrak::adapters::SyntaxHighlighterAdapter;
449 use lazy_static::lazy_static;
450 use log::debug;
451- use tree_sitter_highlight::{HighlightConfiguration, Highlighter as TSHighlighter, HtmlRenderer};
452- use tree_sitter::Language;
453 use tera::escape_html;
454+ use tree_sitter::Language;
455+ use tree_sitter_highlight::{HighlightConfiguration, Highlighter as TSHighlighter, HtmlRenderer};
456
457 use crate::config::TreeSitterParser;
458 use crate::languages::{Hint, LANGUAGE_TABLE};
459 @@ -78,39 +78,36 @@ impl Loader {
460
461 fn try_query_load(&self, path: &str, hint: &Hint) -> Result<(), Error> {
462 let query_base_path = Path::new(path).join(hint.0.to_string().to_lowercase());
463- match fs::read_dir(query_base_path) {
464- Ok(queries) => {
465- for query in queries {
466- let query = query?;
467- match query.file_name().into_string().unwrap().as_str() {
468- "highlights.scm" => {
469- let highlight_scm = fs::read_to_string(query.path())?;
470- HIGHLIGHTS
471- .write()
472- .unwrap()
473- .insert(hint.clone(), highlight_scm);
474- debug!("loaded [{:?}] {}", hint, query.path().display());
475- }
476- "locals.scm" => {
477- let locals_scm = fs::read_to_string(query.path())?;
478- LOCALS.write().unwrap().insert(hint.clone(), locals_scm);
479- debug!("loaded [{:?}] {}", hint, query.path().display());
480- }
481- "injections.scm" => {
482- let injections_scm = fs::read_to_string(query.path())?;
483- INJECTIONS
484- .write()
485- .unwrap()
486- .insert(hint.clone(), injections_scm);
487- debug!("loaded [{:?}] {}", hint, query.path().display());
488- }
489- name => {
490- debug!("ignoring query file: {}", name)
491- }
492+ if let Ok(queries) = fs::read_dir(query_base_path) {
493+ for query in queries {
494+ let query = query?;
495+ match query.file_name().into_string().unwrap().as_str() {
496+ "highlights.scm" => {
497+ let highlight_scm = fs::read_to_string(query.path())?;
498+ HIGHLIGHTS
499+ .write()
500+ .unwrap()
501+ .insert(hint.clone(), highlight_scm);
502+ debug!("loaded [{:?}] {}", hint, query.path().display());
503+ }
504+ "locals.scm" => {
505+ let locals_scm = fs::read_to_string(query.path())?;
506+ LOCALS.write().unwrap().insert(hint.clone(), locals_scm);
507+ debug!("loaded [{:?}] {}", hint, query.path().display());
508+ }
509+ "injections.scm" => {
510+ let injections_scm = fs::read_to_string(query.path())?;
511+ INJECTIONS
512+ .write()
513+ .unwrap()
514+ .insert(hint.clone(), injections_scm);
515+ debug!("loaded [{:?}] {}", hint, query.path().display());
516+ }
517+ name => {
518+ debug!("ignoring query file: {}", name)
519 }
520 }
521 }
522- Err(_) => {}
523 }
524 Ok(())
525 }
526 @@ -181,6 +178,21 @@ impl Loader {
527 }
528 }
529
530+ fn render_lines(lines: Vec<&str>, show_line_numbers: bool) -> String {
531+ let buf = Vec::new();
532+ let mut file = Cursor::new(buf);
533+ write!(&mut file, "<table>").unwrap();
534+ for (i, line) in lines.into_iter().enumerate() {
535+ if show_line_numbers {
536+ write!(&mut file, "<tr><td class=line-number>{:?}</td>", i + 1).unwrap();
537+ }
538+ write!(&mut file, "<td class=line>{}</td></tr>", line).unwrap();
539+ }
540+ write!(&mut file, "</table>").unwrap();
541+ let bytes = file.into_inner();
542+ String::from_utf8(bytes).unwrap()
543+ }
544+
545 #[derive(Clone, Debug)]
546 pub struct Highlighter {
547 names: Vec<String>,
548 @@ -200,21 +212,6 @@ impl Highlighter {
549 }
550 }
551
552- fn from_lines(&self, lines: Vec<&str>, show_line_numbers: bool) -> String {
553- let buf = Vec::new();
554- let mut file = Cursor::new(buf);
555- write!(&mut file, "<table>").unwrap();
556- for (i, line) in lines.into_iter().enumerate() {
557- if show_line_numbers {
558- write!(&mut file, "<tr><td class=line-number>{:?}</td>", i + 1).unwrap();
559- }
560- write!(&mut file, "<td class=line>{}</td></tr>", line).unwrap();
561- }
562- write!(&mut file, "</table>").unwrap();
563- let bytes = file.into_inner();
564- String::from_utf8(bytes).unwrap()
565- }
566-
567 pub fn highlight(
568 &self,
569 code: &str,
570 @@ -245,12 +242,14 @@ impl Highlighter {
571 let injections = INJECTIONS
572 .read()
573 .unwrap()
574- .get(&hint).cloned()
575+ .get(&hint)
576+ .cloned()
577 .unwrap_or_default();
578 let locals = LOCALS
579 .read()
580 .unwrap()
581- .get(&hint).cloned()
582+ .get(&hint)
583+ .cloned()
584 .unwrap_or_default();
585 let mut config =
586 HighlightConfiguration::new(*language, syntax, &injections, &locals)
587 @@ -278,20 +277,14 @@ impl Highlighter {
588
589 (
590 Some(hint.clone()),
591- self.from_lines(
592- renderer.lines().collect(),
593- show_line_numbers,
594- ),
595+ render_lines(renderer.lines().collect(), show_line_numbers),
596 )
597 }
598 _ => {
599 debug!("cannot paint syntax for language: {:?}", hint);
600 (
601 None,
602- self.from_lines(
603- escape_html(code).lines().collect(),
604- show_line_numbers,
605- ),
606+ render_lines(escape_html(code).lines().collect(), show_line_numbers),
607 )
608 }
609 },
610 @@ -299,10 +292,7 @@ impl Highlighter {
611 debug!("cannot determine language");
612 (
613 None,
614- self.from_lines(
615- code.to_string().lines().collect(),
616- show_line_numbers,
617- ),
618+ render_lines(code.to_string().lines().collect(), show_line_numbers),
619 )
620 }
621 }
622 diff --git a/src/web2/middleware/error.rs b/src/web2/middleware/error.rs
623index cecd638..956589e 100644
624--- a/src/web2/middleware/error.rs
625+++ b/src/web2/middleware/error.rs
626 @@ -1,8 +1,7 @@
627-
628 use std::sync::Arc;
629
630 use axum::{
631- extract::{Request, State},
632+ extract,
633 http::StatusCode,
634 middleware::Next,
635 response::{Html, IntoResponse, Response},
636 @@ -14,10 +13,12 @@ use crate::web2::error::Error;
637 use crate::web2::extractors::config::ConfigReader;
638 use crate::web2::terautil::{Loader, Options};
639
640+ pub type State = Arc<(Config, Vec<(String, Tera)>)>;
641+
642 pub async fn middleware(
643- State(state): State<Arc<(Config, Vec<(String, Tera)>)>>,
644+ extract::State(state): extract::State<State>,
645 ConfigReader(client_config): ConfigReader,
646- req: Request,
647+ req: extract::Request,
648 next: Next,
649 ) -> Response {
650 let response = next.run(req).await;
651 diff --git a/src/web2/middleware/rpc_initiator.rs b/src/web2/middleware/rpc_initiator.rs
652index 78d2642..cdc455c 100644
653--- a/src/web2/middleware/rpc_initiator.rs
654+++ b/src/web2/middleware/rpc_initiator.rs
655 @@ -3,7 +3,7 @@ use std::sync::Arc;
656 use crate::web2::terautil::{Loader, Options};
657 use axum::{
658 body::Body,
659- extract::{Request, State},
660+ extract,
661 http::{header::CONTENT_TYPE, StatusCode},
662 middleware::Next,
663 response::Response,
664 @@ -17,6 +17,8 @@ use crate::web2::config::Config as ClientConfig;
665 use crate::web2::extractors::config::ConfigReader;
666 use ayllu_rpc::Client;
667
668+ pub type State = Arc<(Config, Vec<(String, Tera)>, &'static [Kind])>;
669+
670 pub enum Kind {
671 Mail,
672 Xmpp,
673 @@ -41,12 +43,12 @@ fn plugin_not_enabled(
674 plugin_name: &str,
675 cfg: &Config,
676 client_config: ClientConfig,
677- templates: &Vec<(String, Tera)>,
678+ templates: &[(String, Tera)],
679 ) -> Response {
680 log::warn!("returning error due to missing plugin: {}", plugin_name);
681 // reload the theme since the middleware may not have ran yet
682 let loader = Loader {
683- templates: templates.clone(),
684+ templates: templates.to_vec(),
685 default_theme: cfg.web.default_theme.clone(),
686 };
687 let (template, mut ctx) = loader.load(
688 @@ -71,7 +73,11 @@ fn plugin_not_enabled(
689 }
690
691 /// configure an optinoal plugin for use in a handler
692- pub async fn optional(State(cfg): State<Arc<Config>>, mut req: Request, next: Next) -> Response {
693+ pub async fn optional(
694+ extract::State(cfg): extract::State<Arc<Config>>,
695+ mut req: extract::Request,
696+ next: Next,
697+ ) -> Response {
698 req.extensions_mut().insert(Initiator {
699 mail: cfg
700 .mail
701 @@ -88,9 +94,9 @@ pub async fn optional(State(cfg): State<Arc<Config>>, mut req: Request, next: Ne
702 /// configure a required plugin for use in a handler, if it is not configured
703 /// an error page will be returned from the handler
704 pub async fn required(
705- State(state): State<Arc<(Config, Vec<(String, Tera)>, &'static [Kind])>>,
706+ extract::State(state): extract::State<State>,
707 ConfigReader(client_config): ConfigReader,
708- mut req: Request,
709+ mut req: extract::Request,
710 next: Next,
711 ) -> Response {
712 let mut initiator = Initiator::default();
713 diff --git a/src/web2/middleware/sites.rs b/src/web2/middleware/sites.rs
714index eecd85b..c711298 100644
715--- a/src/web2/middleware/sites.rs
716+++ b/src/web2/middleware/sites.rs
717 @@ -3,7 +3,7 @@ use std::path::{Path, PathBuf};
718
719 use axum::{
720 body::Body,
721- extract::{Request, State},
722+ extract,
723 http::{header, StatusCode},
724 middleware::Next,
725 response::{IntoResponse, Response},
726 @@ -16,6 +16,9 @@ use crate::web2::error::Error;
727 use crate::web2::util;
728 use ayllu_git::Wrapper as Repository;
729
730+ pub type State = extract::State<(Config, Vec<(String, (String, String))>)>;
731+ pub type Sites = Vec<(String, (String, String))>;
732+
733 // array of all the repositories in each collection
734 fn repositories(collections: Vec<Collection>) -> Result<Vec<PathBuf>, Error> {
735 let mut paths: Vec<PathBuf> = Vec::new();
736 @@ -32,7 +35,7 @@ fn repositories(collections: Vec<Collection>) -> Result<Vec<PathBuf>, Error> {
737 Ok(paths)
738 }
739
740- pub fn sites(cfg: Config) -> Result<Vec<(String, (String, String))>, Error> {
741+ pub fn sites(cfg: Config) -> Result<Sites, Error> {
742 let mut sites: Vec<(String, (String, String))> = Vec::new();
743 for path in repositories(cfg.collections.clone())? {
744 let repository = Repository::new(path.as_path())?;
745 @@ -53,8 +56,8 @@ pub fn sites(cfg: Config) -> Result<Vec<(String, (String, String))>, Error> {
746 }
747
748 pub async fn middleware(
749- State((cfg, sites)): State<(Config, Vec<(String, (String, String))>)>,
750- req: Request,
751+ extract::State((cfg, sites)): State,
752+ req: extract::Request,
753 next: Next,
754 ) -> Result<Response, Error> {
755 if !cfg.sites.enabled {
756 @@ -82,7 +85,7 @@ pub async fn middleware(
757
758 match repo_path {
759 Some(repo_path) => {
760- let repository = Repository::from_str(&repo_path)?;
761+ let repository = Repository::new(Path::new(&repo_path))?;
762 let config = repository.config()?;
763 let path = util::select_path(req.uri(), Some(0), None).map_or_else(
764 || PathBuf::from("index.html"),
765 diff --git a/src/web2/middleware/template.rs b/src/web2/middleware/template.rs
766index e93c093..58f93cc 100644
767--- a/src/web2/middleware/template.rs
768+++ b/src/web2/middleware/template.rs
769 @@ -2,18 +2,14 @@ use serde::Deserialize;
770 use std::sync::Arc;
771 use tera::{Context as TeraContext, Tera};
772
773- use axum::{
774- extract::Request,
775- extract::{Path, State},
776- middleware::Next,
777- response::Response,
778- };
779+ use axum::{extract, middleware::Next, response::Response};
780
781 use crate::config::Config;
782 use crate::web2::extractors::config::ConfigReader;
783 use crate::web2::terautil::{Loader, Options};
784
785 pub type Template = (Tera, TeraContext);
786+ pub type State = extract::State<Arc<(Config, Vec<(String, Tera)>)>>;
787
788 #[derive(Deserialize)]
789 pub struct CommonParams {
790 @@ -22,10 +18,10 @@ pub struct CommonParams {
791 }
792
793 pub async fn middleware(
794- State(state): State<Arc<(Config, Vec<(String, Tera)>)>>,
795+ extract::State(state): State,
796 ConfigReader(config): ConfigReader,
797- Path(CommonParams { collection, name }): Path<CommonParams>,
798- mut req: Request,
799+ extract::Path(CommonParams { collection, name }): extract::Path<CommonParams>,
800+ mut req: extract::Request,
801 next: Next,
802 ) -> Response {
803 let loader = Loader {
804 diff --git a/src/web2/routes/blob.rs b/src/web2/routes/blob.rs
805index cc0dbad..9cb8fff 100644
806--- a/src/web2/routes/blob.rs
807+++ b/src/web2/routes/blob.rs
808 @@ -76,12 +76,9 @@ pub async fn serve(
809 let file_path = preamble.file_path_string();
810 let (hint, content) =
811 highlighter.highlight(&content, Some(file_path.as_str()), None, None, true);
812- match hint {
813- Some(hint) => {
814- ctx.insert("color", &LANGUAGE_TABLE.color_from_language(&hint));
815- ctx.insert("hint", &hint);
816- }
817- None => {}
818+ if let Some(hint) = hint {
819+ ctx.insert("color", &LANGUAGE_TABLE.color_from_language(&hint));
820+ ctx.insert("hint", &hint);
821 }
822 ctx.insert("content", &content);
823 }
824 diff --git a/src/web2/routes/repo.rs b/src/web2/routes/repo.rs
825index 4a5102d..4482e9b 100644
826--- a/src/web2/routes/repo.rs
827+++ b/src/web2/routes/repo.rs
828 @@ -147,45 +147,42 @@ async fn managed_chats(cfg: &Config, git_cfg: &GitConfig, initiator: Initiator)
829 }
830 }
831 let xmpp_client = initiator.client(InitiatorKind::Xmpp);
832- match xmpp_client {
833- Some(xmpp_client) => {
834- match xmpp_client
835- .invoke(move |client: XmppClient| async move {
836- let mut remote_channels: Vec<ChatLink> = Vec::new();
837- let mut request = client.stats_request();
838- let mut channels = request
839- .get()
840- .init_channels(managed_xmpp_channels.len() as u32);
841- for (i, lookup) in to_lookup.iter().enumerate() {
842- channels.set(i as u32, lookup.as_str().into());
843- }
844- let status = request.send().promise.await?;
845- let results = status.get()?;
846- let stats = results.get_stats()?;
847- for stat in stats {
848- remote_channels.push(ChatLink {
849- kind: format!("{:?}", ChatKind::Xmpp),
850- url: stat.get_name()?.to_string().unwrap(),
851- description: None,
852- users_online: Some(stat.get_users_online()),
853- })
854- }
855- Ok(remote_channels)
856- })
857- .await
858- {
859- Ok(remote_links) => {
860- links.extend(remote_links);
861+ if let Some(xmpp_client) = xmpp_client {
862+ match xmpp_client
863+ .invoke(move |client: XmppClient| async move {
864+ let mut remote_channels: Vec<ChatLink> = Vec::new();
865+ let mut request = client.stats_request();
866+ let mut channels = request
867+ .get()
868+ .init_channels(managed_xmpp_channels.len() as u32);
869+ for (i, lookup) in to_lookup.iter().enumerate() {
870+ channels.set(i as u32, lookup.as_str().into());
871 }
872- Err(err) => {
873- log::error!(
874- "failed to resolve discussion group status: {:?}",
875- err.to_string()
876- )
877+ let status = request.send().promise.await?;
878+ let results = status.get()?;
879+ let stats = results.get_stats()?;
880+ for stat in stats {
881+ remote_channels.push(ChatLink {
882+ kind: format!("{:?}", ChatKind::Xmpp),
883+ url: stat.get_name()?.to_string().unwrap(),
884+ description: None,
885+ users_online: Some(stat.get_users_online()),
886+ })
887 }
888- };
889- }
890- None => {}
891+ Ok(remote_channels)
892+ })
893+ .await
894+ {
895+ Ok(remote_links) => {
896+ links.extend(remote_links);
897+ }
898+ Err(err) => {
899+ log::error!(
900+ "failed to resolve discussion group status: {:?}",
901+ err.to_string()
902+ )
903+ }
904+ };
905 }
906 }
907 links
908 diff --git a/src/web2/routes/rss.rs b/src/web2/routes/rss.rs
909index 8a1387b..2597a6d 100644
910--- a/src/web2/routes/rss.rs
911+++ b/src/web2/routes/rss.rs
912 @@ -41,9 +41,9 @@ fn html_commit(summary: String, message: String) -> String {
913 /// Timeframe specifies a chunk of time to summarize repository activity for.
914 #[derive(Clone, Copy)]
915 enum Timeframe {
916- DAILY,
917- WEEKLY,
918- MONTHLY,
919+ Daily,
920+ Weekly,
921+ Monthly,
922 }
923
924 impl Timeframe {
925 @@ -53,14 +53,14 @@ impl Timeframe {
926 /// 7 day week from the "current" week.
927 pub fn clamp(self, current_time: OffsetDateTime) -> (OffsetDateTime, OffsetDateTime) {
928 match self {
929- Timeframe::DAILY => {
930+ Timeframe::Daily => {
931 // today at midnight
932 let end_time = current_time.replace_time(time!(00:00:00));
933 // 00:00:00 two days ago
934 let start_time = end_time.saturating_sub(24 * Duration::HOUR);
935 (start_time, end_time)
936 }
937- Timeframe::WEEKLY => {
938+ Timeframe::Weekly => {
939 // today at midnight
940 let current_time = current_time.replace_time(time!(00:00:00));
941 // days to previous sunday
942 @@ -70,7 +70,7 @@ impl Timeframe {
943 let start_time = end_time.saturating_sub(7 * Duration::DAY);
944 (start_time, end_time)
945 }
946- Timeframe::MONTHLY => {
947+ Timeframe::Monthly => {
948 // today at midnight
949 let current_time = current_time.replace_time(time!(00:00:00));
950 let days_to_previous_month = current_time.date().day();
951 @@ -133,11 +133,8 @@ impl Builder {
952 .map_or(String::new(), |date| date.to_string())
953 }));
954 channel.set_items(items);
955- match self.time_to_live {
956- Some(ttl) => {
957- channel.set_ttl(ttl.whole_minutes().to_string());
958- }
959- None => {}
960+ if let Some(ttl) = self.time_to_live {
961+ channel.set_ttl(ttl.whole_minutes().to_string());
962 }
963 channel
964 }
965 @@ -295,9 +292,7 @@ pub async fn feed_firehose(
966 context,
967 origin: cfg.origin,
968 title: String::from("Firehose"),
969- time_to_live: cfg
970- .rss_time_to_live
971- .map(Duration::seconds),
972+ time_to_live: cfg.rss_time_to_live.map(Duration::seconds),
973 current_time: OffsetDateTime::now_utc(),
974 };
975 let channel = builder.firehose(
976 @@ -320,14 +315,12 @@ pub async fn feed_1d(
977 context,
978 origin: cfg.origin,
979 title: String::from("Daily Update Summary"),
980- time_to_live: cfg
981- .rss_time_to_live
982- .map(Duration::seconds),
983+ time_to_live: cfg.rss_time_to_live.map(Duration::seconds),
984 current_time: OffsetDateTime::now_utc(),
985 };
986 let channel = builder.summary(
987 builder.scan_repositories(cfg.collections)?,
988- Timeframe::DAILY,
989+ Timeframe::Daily,
990 )?;
991 let response = Response::builder()
992 .header(CONTENT_TYPE, TEXT_XML.as_ref())
993 @@ -345,14 +338,12 @@ pub async fn feed_1w(
994 context,
995 origin: cfg.origin,
996 title: String::from("Weekly Update Summary"),
997- time_to_live: cfg
998- .rss_time_to_live
999- .map(Duration::seconds),
1000+ time_to_live: cfg.rss_time_to_live.map(Duration::seconds),
1001 current_time: OffsetDateTime::now_utc(),
1002 };
1003 let channel = builder.summary(
1004 builder.scan_repositories(cfg.collections)?,
1005- Timeframe::WEEKLY,
1006+ Timeframe::Weekly,
1007 )?;
1008 let response = Response::builder()
1009 .header(CONTENT_TYPE, TEXT_XML.as_ref())
1010 @@ -370,14 +361,12 @@ pub async fn feed_1m(
1011 context,
1012 origin: cfg.origin,
1013 title: String::from("Monthly Update Summary"),
1014- time_to_live: cfg
1015- .rss_time_to_live
1016- .map(Duration::seconds),
1017+ time_to_live: cfg.rss_time_to_live.map(Duration::seconds),
1018 current_time: OffsetDateTime::now_utc(),
1019 };
1020 let channel = builder.summary(
1021 builder.scan_repositories(cfg.collections)?,
1022- Timeframe::MONTHLY,
1023+ Timeframe::Monthly,
1024 )?;
1025 let response = Response::builder()
1026 .header(CONTENT_TYPE, TEXT_XML.as_ref())
1027 @@ -401,9 +390,7 @@ pub async fn feed_repository_firehose(
1028 "Firehose for {}/{}",
1029 preamble.collection_name, preamble.repo_name
1030 ),
1031- time_to_live: cfg
1032- .rss_time_to_live
1033- .map(Duration::seconds),
1034+ time_to_live: cfg.rss_time_to_live.map(Duration::seconds),
1035 current_time: OffsetDateTime::now_utc(),
1036 };
1037 let channel = builder.firehose(vec![(preamble.repo_path, project_url)], Duration::days(7))?;
1038 @@ -429,12 +416,10 @@ pub async fn feed_repository_1d(
1039 "Daily Update Summary for {}/{}",
1040 preamble.collection_name, preamble.repo_name
1041 ),
1042- time_to_live: cfg
1043- .rss_time_to_live
1044- .map(Duration::seconds),
1045+ time_to_live: cfg.rss_time_to_live.map(Duration::seconds),
1046 current_time: OffsetDateTime::now_utc(),
1047 };
1048- let channel = builder.summary(vec![(preamble.repo_path, project_url)], Timeframe::DAILY)?;
1049+ let channel = builder.summary(vec![(preamble.repo_path, project_url)], Timeframe::Daily)?;
1050 let response = Response::builder()
1051 .header(CONTENT_TYPE, TEXT_XML.as_ref())
1052 .body(Body::new(stylesheet_hack(channel.to_string())))
1053 @@ -457,12 +442,10 @@ pub async fn feed_repository_1w(
1054 "Weekly Update Summary for {}/{}",
1055 preamble.collection_name, preamble.repo_name
1056 ),
1057- time_to_live: cfg
1058- .rss_time_to_live
1059- .map(Duration::seconds),
1060+ time_to_live: cfg.rss_time_to_live.map(Duration::seconds),
1061 current_time: OffsetDateTime::now_utc(),
1062 };
1063- let channel = builder.summary(vec![(preamble.repo_path, project_url)], Timeframe::WEEKLY)?;
1064+ let channel = builder.summary(vec![(preamble.repo_path, project_url)], Timeframe::Weekly)?;
1065 let response = Response::builder()
1066 .header(CONTENT_TYPE, TEXT_XML.as_ref())
1067 .body(Body::new(stylesheet_hack(channel.to_string())))
1068 @@ -485,12 +468,10 @@ pub async fn feed_repository_1m(
1069 "Monthly Update Summary for {}/{}",
1070 preamble.collection_name, preamble.repo_name
1071 ),
1072- time_to_live: cfg
1073- .rss_time_to_live
1074- .map(Duration::seconds),
1075+ time_to_live: cfg.rss_time_to_live.map(Duration::seconds),
1076 current_time: OffsetDateTime::now_utc(),
1077 };
1078- let channel = builder.summary(vec![(preamble.repo_path, project_url)], Timeframe::MONTHLY)?;
1079+ let channel = builder.summary(vec![(preamble.repo_path, project_url)], Timeframe::Monthly)?;
1080 let response = Response::builder()
1081 .header(CONTENT_TYPE, TEXT_XML.as_ref())
1082 .body(Body::new(stylesheet_hack(channel.to_string())))
1083 @@ -612,7 +593,7 @@ mod tests {
1084 .as_ref()
1085 .is_some_and(|title| title == "Commit: commit 1"));
1086 assert!(channel.items[0].guid.is_some());
1087- // FIXME: assert!(channel.items[1].guid.is_some());
1088+ // FIXME: assert!(channel.items[1].guid.is_some());
1089 assert!(channel.items[2].guid.is_some());
1090 test_repo.cleanup().expect("failed to cleanup repo");
1091 }
1092 @@ -650,7 +631,7 @@ mod tests {
1093 current_time: OffsetDateTime::parse(CURRENT_TIME, &Rfc2822).unwrap(),
1094 };
1095 let channel = builder
1096- .summary(vec![(path, name)], Timeframe::DAILY)
1097+ .summary(vec![(path, name)], Timeframe::Daily)
1098 .expect("failed to build items");
1099 assert!(channel.items.len() == 1);
1100 assert!(channel.items[0]
1101 @@ -688,7 +669,7 @@ mod tests {
1102 current_time: OffsetDateTime::parse(CURRENT_TIME, &Rfc2822).unwrap(),
1103 };
1104 let channel = builder
1105- .summary(vec![(path, name)], Timeframe::WEEKLY)
1106+ .summary(vec![(path, name)], Timeframe::Weekly)
1107 .expect("failed to build items");
1108 assert!(channel.items.len() == 1);
1109 assert!(channel.items[0]
1110 @@ -732,7 +713,7 @@ mod tests {
1111 current_time: OffsetDateTime::parse(CURRENT_TIME, &Rfc2822).unwrap(),
1112 };
1113 let channel = builder
1114- .summary(vec![(path, name)], Timeframe::MONTHLY)
1115+ .summary(vec![(path, name)], Timeframe::Monthly)
1116 .expect("failed to build items");
1117 assert!(channel.items.len() == 1);
1118 assert!(channel.items[0]
1119 @@ -766,7 +747,7 @@ mod tests {
1120 current_time: OffsetDateTime::parse(CURRENT_TIME, &Rfc2822).unwrap(),
1121 };
1122 let channel = builder
1123- .summary(vec![(path, name)], Timeframe::DAILY)
1124+ .summary(vec![(path, name)], Timeframe::Daily)
1125 .expect("failed to build items");
1126 assert!(channel.items.is_empty());
1127 assert!(channel.ttl.as_ref().is_some_and(|ttl| ttl == "60"))
1128 @@ -775,7 +756,7 @@ mod tests {
1129 #[test]
1130 fn test_timeframe_1d() {
1131 let now = datetime!(2021-03-05 13:00:55 UTC);
1132- let (start, end) = Timeframe::DAILY.clamp(now);
1133+ let (start, end) = Timeframe::Daily.clamp(now);
1134 assert!(start == datetime!(2021-03-04 00:00:00 UTC));
1135 assert!(end == datetime!(2021-03-05 00:00:00 UTC));
1136 }
1137 @@ -783,11 +764,11 @@ mod tests {
1138 #[test]
1139 fn test_timeframe_1w() {
1140 let now = datetime!(2021-03-05 13:00:55 UTC);
1141- let (start, end) = Timeframe::WEEKLY.clamp(now);
1142+ let (start, end) = Timeframe::Weekly.clamp(now);
1143 assert!(start == datetime!(2021-02-21 00:00:00 UTC));
1144 assert!(end == datetime!(2021-02-28 00:00:00 UTC));
1145 let now = datetime!(2021-03-07 21:34:05 UTC);
1146- let (start, end) = Timeframe::WEEKLY.clamp(now);
1147+ let (start, end) = Timeframe::Weekly.clamp(now);
1148 assert!(start == datetime!(2021-02-28 00:00:00 UTC));
1149 assert!(end == datetime!(2021-03-07 00:00:00 UTC));
1150 }
1151 @@ -795,11 +776,11 @@ mod tests {
1152 #[test]
1153 fn test_timeframe_1m() {
1154 let now = datetime!(2021-03-05 13:00:55 UTC);
1155- let (start, end) = Timeframe::MONTHLY.clamp(now);
1156+ let (start, end) = Timeframe::Monthly.clamp(now);
1157 assert!(start == datetime!(2021-02-01 00:00:00 UTC));
1158 assert!(end == datetime!(2021-02-28 00:00:00 UTC));
1159 let now = datetime!(2021-01-05 13:00:55 UTC);
1160- let (start, end) = Timeframe::MONTHLY.clamp(now);
1161+ let (start, end) = Timeframe::Monthly.clamp(now);
1162 assert!(start == datetime!(2020-12-01 00:00:00 UTC));
1163 assert!(end == datetime!(2020-12-31 00:00:00 UTC));
1164 }
1165 diff --git a/src/web2/server.rs b/src/web2/server.rs
1166index e8cb16f..60dff65 100644
1167--- a/src/web2/server.rs
1168+++ b/src/web2/server.rs
1169 @@ -46,7 +46,7 @@ use crate::web2::routes::robots;
1170 use crate::web2::routes::rss;
1171 use crate::web2::routes::xmpp;
1172 use crate::web2::terautil;
1173- use ayllu_database::Wrapper as Database;
1174+ use ayllu_database::Builder;
1175
1176 pub async fn serve(cfg: &Config) -> Result<(), Box<dyn Error>> {
1177 let keywords = match &cfg.tree_sitter {
1178 @@ -90,12 +90,12 @@ pub async fn serve(cfg: &Config) -> Result<(), Box<dyn Error>> {
1179 let highlighter = Highlighter::new(&keywords);
1180 let adapter = TreeSitterAdapter::new(highlighter.clone());
1181
1182- let db = Database::new(
1183- &cfg.database.path,
1184- cfg.database.migrate,
1185- cfg.log_level == "DEBUG",
1186- )
1187- .await?;
1188+ let db = Builder::default()
1189+ .url(&cfg.database.path)
1190+ .log_queries(cfg.log_level == "DEBUG")
1191+ .read_only(true) // Web UI is 100% read-only
1192+ .build()
1193+ .await?;
1194
1195 // NOTE: files modified on the file system will see their changes
1196 // immediately in the rendered server output however added new files