diff options
author | Kornel <kornel@geekhood.net> | 2020-03-03 12:37:27 +0000 |
---|---|---|
committer | Kornel <kornel@geekhood.net> | 2020-03-10 00:43:41 +0000 |
commit | c516d4775a9d1db845db69e3917c371f894c1e50 (patch) | |
tree | 475d31da6fda0ce059b9e9e0b0372ead285735c9 | |
parent | 68044807289ac950b94fa0d02c58d7531b127863 (diff) |
author
-rw-r--r-- | crate_db/src/lib_crate_db.rs | 22 | ||||
-rw-r--r-- | front_end/src/author_page.rs | 24 | ||||
-rw-r--r-- | front_end/templates/author.rs.html | 37 | ||||
-rw-r--r-- | kitchen_sink/src/lib_kitchen_sink.rs | 9 | ||||
-rw-r--r-- | server/src/main.rs | 13 |
5 files changed, 84 insertions, 21 deletions
diff --git a/crate_db/src/lib_crate_db.rs b/crate_db/src/lib_crate_db.rs index 4ddad18..9f0dd01 100644 --- a/crate_db/src/lib_crate_db.rs +++ b/crate_db/src/lib_crate_db.rs @@ -645,21 +645,24 @@ impl CrateDb { pub async fn crates_of_author(&self, github_id: u32) -> FResult<Vec<CrateOwnerRow>> { self.with_read("crates_of_author", |conn| { - let mut query = conn.prepare_cached(r#"SELECT ac.crate_id, ac.invited_by_github_id, ac.invited_at, max(cv.created) - FROM author_crates ac JOIN crate_versions cv USING(crate_id) + let mut query = conn.prepare_cached(r#"SELECT c.origin, ac.invited_by_github_id, ac.invited_at, max(cv.created) + FROM author_crates ac + JOIN crate_versions cv USING(crate_id) + JOIN crates c ON c.id = ac.crate_id WHERE ac.github_id = ?1 GROUP BY ac.crate_id + LIMIT 2000 "#)?; let q = query.query_map(&[&github_id], |row| { - let crate_id: u32 = row.get_unwrap(0); + let origin = Origin::from_str(row.get_raw(0).as_str().unwrap()); let invited_by_github_id: Option<u32> = row.get_unwrap(1); let invited_at = row.get_raw(2).as_str().ok().and_then(|d| DateTime::parse_from_rfc3339(d).ok()); let latest_timestamp: u32 = row.get_unwrap(3); Ok(CrateOwnerRow { - crate_id, + origin, invited_by_github_id, invited_at, - latest_version: DateTime::from_utc(NaiveDateTime::from_timestamp(latest_timestamp as _, 0), FixedOffset::east(0)), + latest_release: DateTime::from_utc(NaiveDateTime::from_timestamp(latest_timestamp as _, 0), FixedOffset::east(0)), }) })?; Ok(q.filter_map(|x| x.ok()).collect()) @@ -1142,11 +1145,12 @@ impl KeywordInsert { } } +#[derive(Debug)] pub struct CrateOwnerRow { - crate_id: u32, - invited_by_github_id: Option<u32>, - invited_at: Option<DateTime<FixedOffset>>, - latest_version: DateTime<FixedOffset>, + pub origin: Origin, + pub invited_by_github_id: Option<u32>, + pub invited_at: Option<DateTime<FixedOffset>>, + pub latest_release: DateTime<FixedOffset>, } #[inline] diff --git a/front_end/src/author_page.rs b/front_end/src/author_page.rs index bf3a548..5bb2ae2 100644 --- a/front_end/src/author_page.rs +++ b/front_end/src/author_page.rs @@ -1,10 +1,15 @@ -use crate::templates; use crate::Page; +use crate::templates; +use futures::stream::StreamExt; +use kitchen_sink::CrateOwnerRow; use kitchen_sink::CResult; use kitchen_sink::KitchenSink; use kitchen_sink::RichAuthor; +use kitchen_sink::RichCrateVersion; +use kitchen_sink::UserOrg; use kitchen_sink::UserType; use render_readme::Renderer; +use std::sync::Arc; // pub struct User { // pub id: u32, @@ -23,17 +28,32 @@ pub struct AuthorPage<'a> { pub aut: &'a RichAuthor, pub kitchen_sink: &'a KitchenSink, pub markup: &'a Renderer, + pub crates: Vec<(Arc<RichCrateVersion>, CrateOwnerRow)>, + pub orgs: Vec<UserOrg>, } impl<'a> AuthorPage<'a> { pub async fn new(aut: &'a RichAuthor, kitchen_sink: &'a KitchenSink, markup: &'a Renderer) -> CResult<AuthorPage<'a>> { dbg!(&aut); - let crates = kitchen_sink.crates_of_author(aut).await?; + let orgs = kitchen_sink.user_github_orgs(&aut.github.login).await?.unwrap_or_default(); + let mut rows = kitchen_sink.crates_of_author(aut).await?; + rows.sort_by(|a,b| b.latest_release.cmp(&a.latest_release)); + rows.truncate(200); + dbg!(&rows); + + let crates = futures::stream::iter(rows.into_iter()) + .filter_map(|row| async move { + let c = kitchen_sink.rich_crate_version_async(&row.origin).await.map_err(|e| eprintln!("{}", e)).ok()?; + Some((c, row)) + }) + .collect().await; Ok(Self { + crates, aut, kitchen_sink, markup, + orgs, }) } diff --git a/front_end/templates/author.rs.html b/front_end/templates/author.rs.html index 244ddf2..bc95198 100644 --- a/front_end/templates/author.rs.html +++ b/front_end/templates/author.rs.html @@ -2,13 +2,13 @@ @use crate::Urler; @use crate::AuthorPage; -@(url: &Urler, c: &AuthorPage) +@(url: &Urler, p: &AuthorPage) -@:base(&c.page(), { +@:base(&p.page(), { <header id="author"> <div class="inner-col"> <div class="breadcrumbs"> - <h1><a href="/">Lib.rs</a></h1> › @if c.is_org() { + <h1><a href="/">Lib.rs</a></h1> › @if p.is_org() { Orgs } else { Users @@ -16,17 +16,42 @@ </div> <h2> - @"@"@c.login() + @"@"@p.login() </h2> <nav><ul> - <li><a href="@c.github_url()">GitHub</a></li> + <li><a href="@p.github_url()">GitHub</a></li> </ul></nav> </div> </header> <main> <div class="inner-col"> - Hi + @if !p.orgs.is_empty() { + <section>Member of GitHub orgs</section> + <ul> + @for org in &p.orgs { + <li><a href="@org.url">@login</a></li> + } + </ul> + } + <section> + <h3>Crates by @p.aut.name()</h3> + <ul class=crates-list> + @for (k, r) in &p.crates { + <li> + <a href="@url.krate(&k)"> + <div class=h> + <h4> + @k.short_name() + </h4> + </div> + <div class=meta> + </div> + </a> + </li> + } + </ul> + </section> </div> </main> </div> diff --git a/kitchen_sink/src/lib_kitchen_sink.rs b/kitchen_sink/src/lib_kitchen_sink.rs index b798bff..4d27ad6 100644 --- a/kitchen_sink/src/lib_kitchen_sink.rs +++ b/kitchen_sink/src/lib_kitchen_sink.rs @@ -2096,6 +2096,15 @@ pub struct RichAuthor { pub github: User, } +impl RichAuthor { + pub fn name(&self) -> &str { + match &self.github.name { + Some(n) if !n.is_empty() => &n, + _ => &self.github.login, + } + } +} + /// This is used to uniquely identify authors based on as little information as is available #[derive(Debug, Hash, Eq, PartialEq)] enum AuthorId { diff --git a/server/src/main.rs b/server/src/main.rs index cd38a34..05b5305 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -445,18 +445,23 @@ async fn handle_author(req: HttpRequest) -> Result<HttpResponse, ServerError> { let login = req.match_info().query("author"); println!("author page for {:?}", login); let state: &AServerState = req.app_data().expect("appdata"); - if !is_alnum(login) { - return render_404_page(state, login); + let crates = state.crates.load(); + let aut = match crates.author_by_login(&login).await { + Ok(aut) => aut, + Err(_) => { + return render_404_page(state, login, "user"); + } + }; + if aut.github.login != login { + return Ok(HttpResponse::PermanentRedirect().header("Location", format!("/~{}", encode(&aut.github.login))).body("")); } let cache_file = state.page_cache_dir.join(format!("@{}.html", login)); Ok(serve_cached( with_file_cache(state, cache_file, 3600, { - let login = login.to_owned(); let state = state.clone(); run_timeout(60, async move { let crates = state.crates.load(); let mut page: Vec<u8> = Vec::with_capacity(32000); - let aut = crates.author_by_login(&login).await?; front_end::render_author_page(&mut page, &aut, &crates, &state.markup).await?; Ok::<_, failure::Error>((page, None)) }) |