From a33dd8ea4065881a8828510a21c670156f9acf20 Mon Sep 17 00:00:00 2001 From: Jordan Doyle Date: Mon, 11 Oct 2021 19:40:35 +0100 Subject: [PATCH] Show 'newly created' on homepage --- chartered-db/src/crates.rs | 26 ++++++++++++++++++++++++++ chartered-db/src/schema.rs | 1 + migrations/2021-08-31-214501_create_crates_table/up.sql | 1 + chartered-frontend/src/pages/Dashboard.tsx | 42 ++++++++++++++++++++++++++++++++++++++++++ chartered-web/src/endpoints/web_api/crates/mod.rs | 2 ++ chartered-web/src/endpoints/web_api/crates/most_downloaded.rs | 4 ++-- chartered-web/src/endpoints/web_api/crates/recently_created.rs | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 124 insertions(+), 4 deletions(-) diff --git a/chartered-db/src/crates.rs b/chartered-db/src/crates.rs index 671fe62..21f4dfc 100644 --- a/chartered-db/src/crates.rs +++ a/chartered-db/src/crates.rs @@ -54,6 +54,7 @@ pub homepage: Option, pub documentation: Option, pub downloads: i32, + pub created_at: chrono::NaiveDateTime, } macro_rules! crate_with_permissions { @@ -160,6 +161,31 @@ .load(&conn)?; Ok(crate_versions.into_iter().into_grouping_map().collect()) + }) + .await? + } + + pub async fn list_recently_created( + conn: ConnectionPool, + requesting_user_id: i32, + ) -> Result> { + tokio::task::spawn_blocking(move || { + let conn = conn.get()?; + + let crates = crate_with_permissions!(requesting_user_id) + .filter( + select_permissions!() + .bitwise_and(UserPermission::VISIBLE.bits()) + .eq(UserPermission::VISIBLE.bits()), + ) + .inner_join(organisations::table) + .inner_join(crate_versions::table) + .select((crates::all_columns, organisations::all_columns)) + .limit(10) + .order_by(crate::schema::crates::dsl::created_at.desc()) + .load(&conn)?; + + Ok(crates) }) .await? } diff --git a/chartered-db/src/schema.rs b/chartered-db/src/schema.rs index 74790d4..10e3e9e 100644 --- a/chartered-db/src/schema.rs +++ a/chartered-db/src/schema.rs @@ -26,6 +26,7 @@ homepage -> Nullable, documentation -> Nullable, downloads -> Integer, + created_at -> Timestamp, } } diff --git a/migrations/2021-08-31-214501_create_crates_table/up.sql b/migrations/2021-08-31-214501_create_crates_table/up.sql index 4d9cd99..ff02770 100644 --- a/migrations/2021-08-31-214501_create_crates_table/up.sql +++ a/migrations/2021-08-31-214501_create_crates_table/up.sql @@ -31,6 +31,7 @@ homepage VARCHAR(255), documentation VARCHAR(255), downloads INTEGER NOT NULL DEFAULT 0, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, UNIQUE (name, organisation_id), FOREIGN KEY (organisation_id) REFERENCES organisations (id) ); diff --git a/chartered-frontend/src/pages/Dashboard.tsx b/chartered-frontend/src/pages/Dashboard.tsx index a7b606e..cf72159 100644 --- a/chartered-frontend/src/pages/Dashboard.tsx +++ a/chartered-frontend/src/pages/Dashboard.tsx @@ -1,10 +1,22 @@ import React = require("react"); import { Link } from "react-router-dom"; import { useAuth } from "../useAuth"; import Nav from "../sections/Nav"; -import {ChevronRight, Download} from "react-bootstrap-icons"; +import {Calendar3, ChevronRight, Download} from "react-bootstrap-icons"; import { useAuthenticatedRequest } from "../util"; +import HumanTime from "react-human-time"; +import {OverlayTrigger, Tooltip} from "react-bootstrap"; + +interface RecentlyCreatedResponse { + crates: RecentlyCreatedResponseVersion[]; +} + +interface RecentlyCreatedResponseVersion { + name: string; + created_at: string; + organisation: string; +} interface RecentlyUpdatedResponse { versions: RecentlyUpdatedResponseVersion[]; @@ -28,6 +40,12 @@ export default function Dashboard() { const auth = useAuth(); + + const { response: recentlyCreated, error: recentlyCreatedError } = + useAuthenticatedRequest({ + auth, + endpoint: "crates/recently-created", + }); const { response: recentlyUpdated, error: recentlyUpdatedError } = useAuthenticatedRequest({ @@ -64,7 +82,27 @@
-

Your Crates

+

Newly Created

+ {(recentlyCreated?.crates || []).map((v) => ( + + + {new Date(v.created_at).toLocaleString()} + + } + > + + {" "} + + + + + ))}
diff --git a/chartered-web/src/endpoints/web_api/crates/mod.rs b/chartered-web/src/endpoints/web_api/crates/mod.rs index e2e3d49..cb526c2 100644 --- a/chartered-web/src/endpoints/web_api/crates/mod.rs +++ a/chartered-web/src/endpoints/web_api/crates/mod.rs @@ -1,8 +1,9 @@ mod info; mod members; mod most_downloaded; mod recently_updated; mod search; +mod recently_created; use axum::{ body::{Body, BoxBody}, @@ -29,6 +30,7 @@ .route("/:org/:crate/members", put(members::handle_put)) .route("/:org/:crate/members", delete(members::handle_delete)) .route("/recently-updated", get(recently_updated::handle)) + .route("/recently-created", get(recently_created::handle)) .route("/most-downloaded", get(most_downloaded::handle)) .route("/search", get(search::handle))) } diff --git a/chartered-web/src/endpoints/web_api/crates/most_downloaded.rs b/chartered-web/src/endpoints/web_api/crates/most_downloaded.rs index cabad18..623a11d 100644 --- a/chartered-web/src/endpoints/web_api/crates/most_downloaded.rs +++ a/chartered-web/src/endpoints/web_api/crates/most_downloaded.rs @@ -24,10 +24,10 @@ extract::Extension(db): extract::Extension, extract::Extension(user): extract::Extension>, ) -> Result, Error> { - let crates_with_versions = Crate::list_most_downloaded(db, user.id).await?; + let crates = Crate::list_most_downloaded(db, user.id).await?; Ok(Json(Response { - crates: crates_with_versions + crates: crates .into_iter() .map(|(crate_, organisation)| ResponseCrate { name: crate_.name, diff --git a/chartered-web/src/endpoints/web_api/crates/recently_created.rs b/chartered-web/src/endpoints/web_api/crates/recently_created.rs new file mode 100644 index 0000000..cff1345 100644 --- /dev/null +++ a/chartered-web/src/endpoints/web_api/crates/recently_created.rs @@ -1,0 +1,52 @@ +use axum::{extract, Json}; +use chartered_db::{crates::Crate, users::User, ConnectionPool}; +use serde::Serialize; +use std::sync::Arc; +use chrono::{DateTime, TimeZone, Utc}; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum Error { + #[error("{0}")] + Database(#[from] chartered_db::Error), +} + +impl Error { + pub fn status_code(&self) -> axum::http::StatusCode { + match self { + Self::Database(e) => e.status_code(), + } + } +} + +define_error_response!(Error); + +pub async fn handle( + extract::Extension(db): extract::Extension, + extract::Extension(user): extract::Extension>, +) -> Result, Error> { + let crates = Crate::list_recently_created(db, user.id).await?; + + Ok(Json(Response { + crates: crates + .into_iter() + .map(|(crate_, organisation)| ResponseCrate { + name: crate_.name, + created_at: chrono::Utc.from_local_datetime(&crate_.created_at).unwrap(), + organisation: organisation.name, + }) + .collect(), + })) +} + +#[derive(Serialize)] +pub struct Response { + crates: Vec, +} + +#[derive(Serialize)] +pub struct ResponseCrate { + name: String, + created_at: DateTime, + organisation: String, +} -- rgit 0.1.3