🏡 index : ~doyle/chartered.git

author Jordan Doyle <jordan@doyle.la> 2021-09-20 1:06:46.0 +01:00:00
committer Jordan Doyle <jordan@doyle.la> 2021-09-20 1:06:46.0 +01:00:00
commit
c8e1cd23f2d3bfda8d8f4e6acf08dd41ba405574 [patch]
tree
08b3158874132cfc82b5b83ae94ac61634e32c44
parent
2581360a901ca83d1b4cfa865ccfdfc892afc002
download
c8e1cd23f2d3bfda8d8f4e6acf08dd41ba405574.tar.gz

Recently updated crates endpoint & UI



Diff

 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<HashMap<Crate, Vec<CrateVersion<'static>>>> {
        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<HashMap<Crate, Vec<CrateVersion<'static>>>> {
    ) -> Result<Vec<(Crate, CrateVersion<'static>)>> {
        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<RecentlyUpdatedResponse>({
    auth,
    endpoint: "crates/recently-updated",
  });

  return (
    <div className="text-white">
@@ -41,25 +44,19 @@
        <hr />

        <div className="row">
          <div className="col-md-4">
          <div className="col-12 col-md-4">
            <h4>Your Crates</h4>
            {recentlyUpdated.map((v) => (
              <CrateCard key={v.name} crate={v} />
            ))}
          </div>

          <div className="col-md-4">
          <div className="col-12 col-md-4">
            <h4>Recently Updated</h4>
            {recentlyUpdated.map((v) => (
            {(recentlyUpdated?.versions || []).map((v) => (
              <CrateCard key={v.name} crate={v} />
            ))}
          </div>

          <div className="col-md-4">
          <div className="col-12 col-md-4">
            <h4>Most Downloaded</h4>
            {recentlyUpdated.map((v) => (
              <CrateCard key={v.name} crate={v} />
            ))}
          </div>
        </div>
      </div>
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<ConnectionPool>,
    extract::Extension(user): extract::Extension<Arc<User>>,
) -> Result<Json<Response>, 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<ResponseVersion>,
}

#[derive(Serialize)]
pub struct ResponseVersion {
    name: String,
    version: String,
}