1use std::{cell::RefCell, sync::Arc};
2
3use anyhow::Context;
4use askama::Template;
5use axum::{
6 Extension,
7 response::{IntoResponse, Response},
8};
9use itertools::{Either, Itertools};
10
11use super::filters;
12use crate::{
13 database::schema::repository::{Repository, YokedRepository},
14 into_response,
15};
16
17#[derive(Template)]
18#[template(path = "index.html")]
19pub struct View<
20 'a,
21 Group: Iterator<Item = (&'a String, &'a YokedRepository)>,
22 GroupIter: Iterator<Item = (&'a str, Group)>,
23> {
24 // this type sig is a necessary evil unfortunately, because askama takes a reference
25 // to the data for rendering.
26 pub repositories: RefCell<Either<GroupIter, std::iter::Empty<(&'a str, Group)>>>,
27}
28
29impl<'a, Group, GroupIter> View<'a, Group, GroupIter>
30where
31 Group: Iterator<Item = (&'a String, &'a YokedRepository)>,
32 GroupIter: Iterator<Item = (&'a str, Group)>,
33{
34 fn take_iter(&self) -> Either<GroupIter, std::iter::Empty<(&'a str, Group)>> {
35 self.repositories.replace(Either::Right(std::iter::empty()))
36 }
37}
38
39pub async fn handle(
40 Extension(db): Extension<Arc<rocksdb::DB>>,
41) -> Result<Response, super::repo::Error> {
42 let fetched = tokio::task::spawn_blocking(move || Repository::fetch_all(&db))
43 .await
44 .context("Failed to join Tokio task")??;
45
46 // rocksdb returned the keys already ordered for us so group_by is a nice
47 // operation we can use here to avoid writing into a map to group. though,
48 // now that i think about it it might act a little bit strangely when mixing
49 // root repositories and nested repositories. we're going to have to prefix
50 // root repositories with a null byte or something. i'll just leave this here
51 // as a TODO.
52 let repositories = fetched
53 .iter()
54 .group_by(|(k, _)| memchr::memrchr(b'/', k.as_bytes()).map_or("", |idx| &k[..idx]));
55
56 Ok(into_response(View {
57 repositories: Either::Left(repositories.into_iter()).into(),
58 })
59 .into_response())
60}