From c8e1cd23f2d3bfda8d8f4e6acf08dd41ba405574 Mon Sep 17 00:00:00 2001 From: Jordan Doyle Date: Mon, 20 Sep 2021 01:06:46 +0100 Subject: [PATCH] Recently updated crates endpoint & UI --- chartered-db/src/crates.rs | 29 +++++++++++++++++++++++++---- chartered-web/src/main.rs | 4 ++++ chartered-frontend/src/pages/Dashboard.tsx | 37 +++++++++++++++++++++---------------- chartered-web/src/endpoints/web_api/ssh_key.rs | 6 ++++-- chartered-web/src/endpoints/web_api/crates/info.rs | 6 ++---- chartered-web/src/endpoints/web_api/crates/mod.rs | 2 ++ chartered-web/src/endpoints/web_api/crates/recently_updated.rs | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 104 insertions(+), 34 deletions(-) diff --git a/chartered-db/src/crates.rs b/chartered-db/src/crates.rs index 3f21e96..1e5de80 100644 --- a/chartered-db/src/crates.rs +++ a/chartered-db/src/crates.rs @@ -1,4 +1,4 @@ -use crate::users::{UserCratePermission, User}; +use crate::users::{User, UserCratePermission}; use super::{ schema::{crate_versions, crates, users}, @@ -21,14 +21,23 @@ } impl Crate { - pub async fn all_with_versions( + pub async fn all_visible_with_versions( conn: ConnectionPool, + given_user_id: i32, ) -> Result>>> { tokio::task::spawn_blocking(move || { let conn = conn.get()?; let crate_versions = crates::table + .inner_join(crate::schema::user_crate_permissions::table) + .filter( + crate::schema::user_crate_permissions::permissions + .bitwise_and(crate::users::UserCratePermissionValue::VISIBLE.bits()) + .ne(0), + ) + .filter(crate::schema::user_crate_permissions::dsl::user_id.eq(given_user_id)) .inner_join(crate_versions::table) + .select((crates::all_columns, crate_versions::all_columns)) .load(&conn)?; Ok(crate_versions.into_iter().into_grouping_map().collect()) @@ -36,14 +45,14 @@ .await? } - pub async fn all_visible_with_versions( + pub async fn list_recently_updated( conn: ConnectionPool, given_user_id: i32, - ) -> Result>>> { + ) -> Result)>> { tokio::task::spawn_blocking(move || { let conn = conn.get()?; - let crate_versions = crates::table + let crates = crates::table .inner_join(crate::schema::user_crate_permissions::table) .filter( crate::schema::user_crate_permissions::permissions @@ -52,10 +61,12 @@ ) .filter(crate::schema::user_crate_permissions::dsl::user_id.eq(given_user_id)) .inner_join(crate_versions::table) + .order_by(crate::schema::crate_versions::dsl::id.desc()) .select((crates::all_columns, crate_versions::all_columns)) + .limit(10) .load(&conn)?; - Ok(crate_versions.into_iter().into_grouping_map().collect()) + Ok(crates) }) .await? } @@ -81,7 +92,9 @@ tokio::task::spawn_blocking(move || { let conn = conn.get()?; - Ok(CrateVersion::belonging_to(&*self).inner_join(users::table).load::<(CrateVersion, User)>(&conn)?) + Ok(CrateVersion::belonging_to(&*self) + .inner_join(users::table) + .load::<(CrateVersion, User)>(&conn)?) }) .await? } @@ -225,7 +238,7 @@ ) -> Result<()> { use crate::schema::crate_versions::dsl::{ checksum, crate_id, crate_versions, dependencies, features, filesystem_object, links, - version, size, user_id, + size, user_id, version, }; use crate::schema::crates::dsl::{ crates, description, documentation, homepage, id, readme, repository, diff --git a/chartered-web/src/main.rs b/chartered-web/src/main.rs index 0db7056..1cfe97a 100644 --- a/chartered-web/src/main.rs +++ a/chartered-web/src/main.rs @@ -87,6 +87,10 @@ "/crates/:crate/members", delete(endpoints::web_api::crates::delete_member) ) + .route( + "/crates/recently-updated", + get(endpoints::web_api::crates::list_recently_updated) + ) .route("/users/search", get(endpoints::web_api::search_users)) .route("/ssh-key", get(endpoints::web_api::get_ssh_keys)) .route("/ssh-key", put(endpoints::web_api::add_ssh_key)) diff --git a/chartered-frontend/src/pages/Dashboard.tsx b/chartered-frontend/src/pages/Dashboard.tsx index ea1ee13..e8aae92 100644 --- a/chartered-frontend/src/pages/Dashboard.tsx +++ a/chartered-frontend/src/pages/Dashboard.tsx @@ -1,23 +1,26 @@ import React = require("react"); import { Link } from "react-router-dom"; import { useAuth } from "../useAuth"; import Nav from "../sections/Nav"; import { ChevronRight } from "react-bootstrap-icons"; +import { useAuthenticatedRequest } from "../util"; +interface RecentlyUpdatedResponse { + versions: RecentlyUpdatedResponseVersions, +} + +interface RecentlyUpdatedResponseVersions { + [i: number]: { name: string, version: string, }, +} + export default function Dashboard() { const auth = useAuth(); - const recentlyUpdated = [ - { - name: "hello-world-rs", - version: "0.0.1", - }, - { - name: "cool-beans-rs", - version: "0.0.1", - }, - ]; + const { response: recentlyUpdated, error } = useAuthenticatedRequest({ + auth, + endpoint: "crates/recently-updated", + }); return (
@@ -41,25 +44,19 @@
-
+

Your Crates

- {recentlyUpdated.map((v) => ( - - ))}
-
+

Recently Updated

- {recentlyUpdated.map((v) => ( + {(recentlyUpdated?.versions || []).map((v) => ( ))}
-
+

Most Downloaded

- {recentlyUpdated.map((v) => ( - - ))}
diff --git a/chartered-web/src/endpoints/web_api/ssh_key.rs b/chartered-web/src/endpoints/web_api/ssh_key.rs index 83bdcd6..d8c7217 100644 --- a/chartered-web/src/endpoints/web_api/ssh_key.rs +++ a/chartered-web/src/endpoints/web_api/ssh_key.rs @@ -1,8 +1,8 @@ use chartered_db::{users::User, ConnectionPool}; use axum::{extract, Json}; use chartered_db::uuid::Uuid; -use chrono::{DateTime, Utc, TimeZone}; +use chrono::{DateTime, TimeZone, Utc}; use log::warn; use serde::{Deserialize, Serialize}; use std::sync::Arc; @@ -40,7 +40,9 @@ }), name: key.name, created_at: Utc.from_local_datetime(&key.created_at).unwrap(), - last_used_at: key.last_used_at.and_then(|v| Utc.from_local_datetime(&v).single()), + last_used_at: key + .last_used_at + .and_then(|v| Utc.from_local_datetime(&v).single()), }) .collect(); diff --git a/chartered-web/src/endpoints/web_api/crates/info.rs b/chartered-web/src/endpoints/web_api/crates/info.rs index c1a8fff..8b85db6 100644 --- a/chartered-web/src/endpoints/web_api/crates/info.rs +++ a/chartered-web/src/endpoints/web_api/crates/info.rs @@ -6,8 +6,8 @@ users::{User, UserCratePermissionValue as Permission}, ConnectionPool, }; -use chrono::TimeZone; use chartered_types::cargo::CrateVersion; +use chrono::TimeZone; use serde::Serialize; use std::sync::Arc; use thiserror::Error; @@ -16,8 +16,6 @@ pub enum Error { #[error("Failed to query database")] Database(#[from] chartered_db::Error), - #[error("Failed to fetch crate file")] - File(#[from] std::io::Error), #[error("{0}")] CrateFetch(#[from] crate::models::crates::CrateFetchError), } @@ -27,7 +25,7 @@ use axum::http::StatusCode; match self { - Self::Database(_) | Self::File(_) => StatusCode::INTERNAL_SERVER_ERROR, + Self::Database(_) => StatusCode::INTERNAL_SERVER_ERROR, Self::CrateFetch(e) => e.status_code(), } } diff --git a/chartered-web/src/endpoints/web_api/crates/mod.rs b/chartered-web/src/endpoints/web_api/crates/mod.rs index 6aa79c9..3113603 100644 --- a/chartered-web/src/endpoints/web_api/crates/mod.rs +++ a/chartered-web/src/endpoints/web_api/crates/mod.rs @@ -1,8 +1,10 @@ mod info; mod members; +mod recently_updated; pub use info::handle as info; pub use members::{ handle_delete as delete_member, handle_get as get_members, handle_patch as update_member, handle_put as insert_member, }; +pub use recently_updated::handle as list_recently_updated; diff --git a/chartered-web/src/endpoints/web_api/crates/recently_updated.rs b/chartered-web/src/endpoints/web_api/crates/recently_updated.rs new file mode 100644 index 0000000..0b2f725 100644 --- /dev/null +++ a/chartered-web/src/endpoints/web_api/crates/recently_updated.rs @@ -1,0 +1,54 @@ +use axum::{extract, Json}; +use chartered_db::{crates::Crate, users::User, ConnectionPool}; +use serde::Serialize; +use std::sync::Arc; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum Error { + #[error("Failed to query database")] + Database(#[from] chartered_db::Error), + #[error("{0}")] + CrateFetch(#[from] crate::models::crates::CrateFetchError), +} + +impl Error { + pub fn status_code(&self) -> axum::http::StatusCode { + use axum::http::StatusCode; + + match self { + Self::Database(_) => StatusCode::INTERNAL_SERVER_ERROR, + Self::CrateFetch(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_with_versions = Crate::list_recently_updated(db, user.id).await?; + + Ok(Json(Response { + versions: crates_with_versions + .into_iter() + .map(|(crate_, version)| ResponseVersion { + name: crate_.name, + version: version.version, + }) + .collect(), + })) +} + +#[derive(Serialize)] +pub struct Response { + versions: Vec, +} + +#[derive(Serialize)] +pub struct ResponseVersion { + name: String, + version: String, +} -- rgit 0.1.3