🏡 index : ~doyle/chartered.git

author Jordan Doyle <jordan@doyle.la> 2021-10-11 19:40:35.0 +01:00:00
committer Jordan Doyle <jordan@doyle.la> 2021-10-11 19:40:35.0 +01:00:00
commit
a33dd8ea4065881a8828510a21c670156f9acf20 [patch]
tree
6cb7b36edd65e3ada78aba43e7c396b647b9fd0c
parent
dae6a84de4fed35acab26e7ed6a84dc3e356708a
download
a33dd8ea4065881a8828510a21c670156f9acf20.tar.gz

Show 'newly created' on homepage



Diff

 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<String>,
    pub documentation: Option<String>,
    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<Vec<(Crate, Organisation)>> {
        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<Text>,
        documentation -> Nullable<Text>,
        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<RecentlyCreatedResponse>({
        auth,
        endpoint: "crates/recently-created",
      });

  const { response: recentlyUpdated, error: recentlyUpdatedError } =
    useAuthenticatedRequest<RecentlyUpdatedResponse>({
@@ -64,7 +82,27 @@

        <div className="row">
          <div className="col-12 col-md-4">
            <h4>Your Crates</h4>
            <h4>Newly Created</h4>
            {(recentlyCreated?.crates || []).map((v) => (
                <CrateCard key={v.name} organisation={v.organisation} name={v.name}>
                  <OverlayTrigger
                      overlay={
                        <Tooltip
                            id={`tooltip-${v.name}-date`}
                        >
                          {new Date(v.created_at).toLocaleString()}
                        </Tooltip>
                      }
                  >
                    <span>
                      <Calendar3 />{" "}
                      <HumanTime
                          time={new Date(v.created_at).getTime()}
                      />
                    </span>
                  </OverlayTrigger>
                </CrateCard>
            ))}
          </div>

          <div className="col-12 col-md-4">
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<ConnectionPool>,
    extract::Extension(user): extract::Extension<Arc<User>>,
) -> Result<Json<Response>, 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<ConnectionPool>,
    extract::Extension(user): extract::Extension<Arc<User>>,
) -> Result<Json<Response>, 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<ResponseCrate>,
}

#[derive(Serialize)]
pub struct ResponseCrate {
    name: String,
    created_at: DateTime<Utc>,
    organisation: String,
}