🏡 index : ~doyle/chartered.git

author Jordan Doyle <jordan@doyle.la> 2021-09-19 20:15:31.0 +01:00:00
committer Jordan Doyle <jordan@doyle.la> 2021-09-19 22:12:26.0 +01:00:00
commit
f72a27b3a5f7e4d0b54549b57d6111308ca557b9 [patch]
tree
08c287266f057ae3234130e31e97a04f1ede2795
parent
377df21e42806d7e9d8535467cdc411217ca8715
download
f72a27b3a5f7e4d0b54549b57d6111308ca557b9.tar.gz

Only keep the latest crate metadata (readme, homepage, etc) around



Diff

 chartered-db/src/crates.rs                              | 144 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------
 chartered-db/src/schema.rs                              |  10 +++++-----
 chartered-git/src/main.rs                               |   2 +-
 migrations/2021-08-31-214501_create_crates_table/up.sql |  12 ++++++------
 chartered-frontend/src/pages/crate/CrateView.tsx        |  30 ++++++++++++++----------------
 chartered-web/src/endpoints/web_api/crates/info.rs      |  55 +++++++++++++++++++++++++++++++++++++++++++++----------
 6 files changed, 131 insertions(+), 122 deletions(-)

diff --git a/chartered-db/src/crates.rs b/chartered-db/src/crates.rs
index 8a19349..c7c172a 100644
--- a/chartered-db/src/crates.rs
+++ a/chartered-db/src/crates.rs
@@ -13,69 +13,12 @@
pub struct Crate {
    pub id: i32,
    pub name: String,
}

#[derive(Identifiable, Queryable, Associations, PartialEq, Debug)]
#[belongs_to(Crate)]
pub struct CrateVersion<'a> {
    pub id: i32,
    pub crate_id: i32,
    pub version: String,
    pub filesystem_object: String,
    pub yanked: bool,
    // TODO: readme should be versioned in the db so we can just pass a reference rather
    // than this massive blob for each version - or just update the readme for the whole
    // crate and don't version it at all? we also need tags, etc too in a pivot table
    pub readme: Option<String>,
    pub description: Option<String>,
    pub repository: Option<String>,
    pub homepage: Option<String>,
    pub documentation: Option<String>,
    pub checksum: String,
    pub dependencies: CrateDependencies<'a>,
    pub features: CrateFeatures,
    pub links: Option<String>,
}

impl<'a> CrateVersion<'a> {
    #[must_use]
    pub fn into_cargo_format(
        self,
        crate_: &'a Crate,
    ) -> (
        chartered_types::cargo::CrateVersion<'a>,
        chartered_types::cargo::CrateVersionMetadata,
    ) {
        (
            chartered_types::cargo::CrateVersion {
                name: crate_.name.as_str().into(),
                vers: self.version.into(),
                deps: self.dependencies.0,
                features: self.features.0,
                links: self.links.map(Into::into),
            },
            chartered_types::cargo::CrateVersionMetadata {
                description: self.description,
                readme: self.readme,
                repository: self.repository,
                homepage: self.homepage,
                documentation: self.documentation,
            },
        )
    }
}

#[derive(Serialize, Deserialize, FromSqlRow, AsExpression, Debug, Clone, PartialEq, Eq)]
#[sql_type = "diesel::sql_types::Blob"]
pub struct CrateDependencies<'a>(pub Vec<chartered_types::cargo::CrateDependency<'a>>);

derive_diesel_json!(CrateDependencies<'a>);

#[derive(Serialize, Deserialize, FromSqlRow, AsExpression, Debug, Clone, PartialEq, Eq)]
#[sql_type = "diesel::sql_types::Blob"]
pub struct CrateFeatures(pub chartered_types::cargo::CrateFeatures);

derive_diesel_json!(CrateFeatures);

impl Crate {
    pub async fn all_with_versions(
@@ -279,29 +222,41 @@
        metadata: chartered_types::cargo::CrateVersionMetadata,
    ) -> Result<()> {
        use crate::schema::crate_versions::dsl::{
            checksum, crate_id, crate_versions, dependencies, description, documentation, features,
            filesystem_object, homepage, links, readme, repository, version,
            checksum, crate_id, crate_versions, dependencies, features, filesystem_object, links,
            version,
        };
        use crate::schema::crates::dsl::{
            crates, description, documentation, homepage, id, readme, repository,
        };

        tokio::task::spawn_blocking(move || {
            let conn = conn.get()?;

            insert_into(crate_versions)
                .values((
                    crate_id.eq(self.id),
                    filesystem_object.eq(file_identifier.to_string()),
                    checksum.eq(file_checksum),
                    version.eq(given.vers),
                    dependencies.eq(CrateDependencies(given.deps)),
                    features.eq(CrateFeatures(given.features)),
                    links.eq(given.links),
                    description.eq(metadata.description),
                    readme.eq(metadata.readme),
                    repository.eq(metadata.repository),
                    homepage.eq(metadata.homepage),
                    documentation.eq(metadata.documentation),
                ))
                .execute(&conn)?;
            conn.transaction::<_, crate::Error, _>(|| {
                diesel::update(crates.filter(id.eq(self.id)))
                    .set((
                        description.eq(metadata.description),
                        readme.eq(metadata.readme),
                        repository.eq(metadata.repository),
                        homepage.eq(metadata.homepage),
                        documentation.eq(metadata.documentation),
                    ))
                    .execute(&conn)?;

                insert_into(crate_versions)
                    .values((
                        crate_id.eq(self.id),
                        filesystem_object.eq(file_identifier.to_string()),
                        checksum.eq(file_checksum),
                        version.eq(given.vers),
                        dependencies.eq(CrateDependencies(given.deps)),
                        features.eq(CrateFeatures(given.features)),
                        links.eq(given.links),
                    ))
                    .execute(&conn)?;

                Ok(())
            })?;

            Ok(())
        })
@@ -330,14 +285,53 @@
            Ok(())
        })
        .await?
    }
}

#[derive(Identifiable, Queryable, Associations, PartialEq, Debug)]
#[belongs_to(Crate)]
pub struct CrateVersion<'a> {
    pub id: i32,
    pub crate_id: i32,
    pub version: String,
    pub filesystem_object: String,
    pub yanked: bool,
    pub checksum: String,
    pub dependencies: CrateDependencies<'a>,
    pub features: CrateFeatures,
    pub links: Option<String>,
}

impl<'a> CrateVersion<'a> {
    #[must_use]
    pub fn into_cargo_format(self, crate_: &'a Crate) -> chartered_types::cargo::CrateVersion<'a> {
        chartered_types::cargo::CrateVersion {
            name: crate_.name.as_str().into(),
            vers: self.version.into(),
            deps: self.dependencies.0,
            features: self.features.0,
            links: self.links.map(Into::into),
        }
    }
}

#[derive(Serialize, Deserialize, FromSqlRow, AsExpression, Debug, Clone, PartialEq, Eq)]
#[sql_type = "diesel::sql_types::Blob"]
pub struct CrateDependencies<'a>(pub Vec<chartered_types::cargo::CrateDependency<'a>>);

derive_diesel_json!(CrateDependencies<'a>);

impl<'a> From<Vec<chartered_types::cargo::CrateDependency<'a>>> for CrateDependencies<'a> {
    fn from(o: Vec<chartered_types::cargo::CrateDependency<'a>>) -> Self {
        Self(o)
    }
}

#[derive(Serialize, Deserialize, FromSqlRow, AsExpression, Debug, Clone, PartialEq, Eq)]
#[sql_type = "diesel::sql_types::Blob"]
pub struct CrateFeatures(pub chartered_types::cargo::CrateFeatures);

derive_diesel_json!(CrateFeatures);

impl<'a> From<chartered_types::cargo::CrateFeatures> for CrateFeatures {
    fn from(o: chartered_types::cargo::CrateFeatures) -> Self {
diff --git a/chartered-db/src/schema.rs b/chartered-db/src/schema.rs
index af75a13..384d4e2 100644
--- a/chartered-db/src/schema.rs
+++ a/chartered-db/src/schema.rs
@@ -5,11 +5,6 @@
        version -> Text,
        filesystem_object -> Text,
        yanked -> Bool,
        readme -> Nullable<Text>,
        description -> Nullable<Text>,
        repository -> Nullable<Text>,
        homepage -> Nullable<Text>,
        documentation -> Nullable<Text>,
        checksum -> Text,
        dependencies -> Binary,
        features -> Binary,
@@ -21,6 +16,11 @@
    crates (id) {
        id -> Integer,
        name -> Text,
        readme -> Nullable<Text>,
        description -> Nullable<Text>,
        repository -> Nullable<Text>,
        homepage -> Nullable<Text>,
        documentation -> Nullable<Text>,
    }
}

diff --git a/chartered-git/src/main.rs b/chartered-git/src/main.rs
index 7e44478..35aaab9 100644
--- a/chartered-git/src/main.rs
+++ a/chartered-git/src/main.rs
@@ -374,7 +374,7 @@
        for version in versions {
            let cksum = version.checksum.clone();
            let yanked = version.yanked;
            let (version, _) = version.into_cargo_format(&crate_def);
            let version = version.into_cargo_format(&crate_def);

            let entry = CrateFileEntry {
                inner: &version,
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 67c5cdb..21d1db1 100644
--- a/migrations/2021-08-31-214501_create_crates_table/up.sql
+++ a/migrations/2021-08-31-214501_create_crates_table/up.sql
@@ -1,6 +1,11 @@
CREATE TABLE crates (
    id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
    name VARCHAR(255) NOT NULL UNIQUE
    name VARCHAR(255) NOT NULL UNIQUE,
    readme TEXT,
    description VARCHAR(255),
    repository VARCHAR(255),
    homepage VARCHAR(255),
    documentation VARCHAR(255)
);

CREATE TABLE crate_versions (
@@ -9,11 +14,6 @@
    version VARCHAR(255) NOT NULL,
    filesystem_object VARCHAR(255) NOT NULL,
    yanked BOOLEAN NOT NULL DEFAULT FALSE,
    readme TEXT,
    description VARCHAR(255),
    repository VARCHAR(255),
    homepage VARCHAR(255),
    documentation VARCHAR(255),
    checksum VARCHAR(255) NOT NULL,
    dependencies BLOB NOT NULL,
    features BLOB NOT NULL,
diff --git a/chartered-frontend/src/pages/crate/CrateView.tsx b/chartered-frontend/src/pages/crate/CrateView.tsx
index 95b4e6b..3c7deb2 100644
--- a/chartered-frontend/src/pages/crate/CrateView.tsx
+++ a/chartered-frontend/src/pages/crate/CrateView.tsx
@@ -18,15 +18,17 @@
type Tab = "readme" | "versions" | "members";

export interface CrateInfo {
  name: string;
  readme?: string;
  description?: string;
  repository?: string;
  homepage?: string;
  documentation?: string;
  versions: CrateInfoVersion[];
}

export interface CrateInfoVersion {
  vers: string;
  homepage: string | null;
  description: string | null;
  documentation: string | null;
  repository: string | null;
  deps: CrateInfoVersionDependency[];
}

@@ -74,7 +76,7 @@
                  <h2 className="text-secondary m-0">{crateVersion.vers}</h2>
                </div>

                <p className="m-0">{crateVersion.description}</p>
                <p className="m-0">{crateInfo.description}</p>
              </div>
            </div>
          </div>
@@ -83,15 +85,13 @@
            <div className="card border-0 shadow-sm text-black h-100">
              <div className="card-body">
                <HouseDoor />{" "}
                <a href={crateVersion.homepage}>{crateVersion.homepage}</a>
                <a href={crateInfo.homepage}>{crateInfo.homepage}</a>
                <br />
                <Book />{" "}
                <a href={crateVersion.documentation}>
                  {crateVersion.documentation}
                </a>
                <a href={crateInfo.documentation}>{crateInfo.documentation}</a>
                <br />
                <Building />{" "}
                <a href={crateVersion.repository}>{crateVersion.repository}</a>
                <a href={crateInfo.repository}>{crateInfo.repository}</a>
              </div>
            </div>
          </div>
@@ -148,11 +148,7 @@
              </div>

              <div className="card-body">
                {currentTab == "readme" ? (
                  <ReadMe crateInfo={crateVersion} />
                ) : (
                  <></>
                )}
                {currentTab == "readme" ? <ReadMe crate={crateInfo} /> : <></>}
                {currentTab == "versions" ? <>Versions</> : <></>}
                {currentTab == "members" ? <Members crate={crate} /> : <></>}
              </div>
@@ -195,10 +191,10 @@
  );
}

function ReadMe(props: { crateInfo: any }) {
function ReadMe(props: { crate: CrateInfo }) {
  return (
    <ReactMarkdown
      children={props.crateInfo.readme}
      children={props.crate.readme}
      remarkPlugins={[remarkGfm]}
      components={{
        code({ node, inline, className, children, ...props }) {
diff --git a/chartered-web/src/endpoints/web_api/crates/info.rs b/chartered-web/src/endpoints/web_api/crates/info.rs
index e4c82f7..bac066c 100644
--- a/chartered-web/src/endpoints/web_api/crates/info.rs
+++ a/chartered-web/src/endpoints/web_api/crates/info.rs
@@ -1,10 +1,12 @@
use crate::models::crates::get_crate_with_permissions;
use axum::{extract, Json};
use axum::{body::Full, extract, response::IntoResponse, Json};
use bytes::Bytes;
use chartered_db::{
    crates::Crate,
    users::{User, UserCratePermissionValue as Permission},
    ConnectionPool,
};
use chartered_types::cargo::{CrateVersion, CrateVersionMetadata};
use chartered_types::cargo::CrateVersion;
use serde::Serialize;
use std::sync::Arc;
use thiserror::Error;
@@ -36,34 +38,51 @@
    extract::Path((_session_key, name)): extract::Path<(String, String)>,
    extract::Extension(db): extract::Extension<ConnectionPool>,
    extract::Extension(user): extract::Extension<Arc<User>>,
) -> Result<Json<Response>, Error> {
) -> Result<axum::http::Response<Full<Bytes>>, Error> {
    let crate_ = get_crate_with_permissions(db.clone(), user, name, &[Permission::VISIBLE]).await?;

    let versions = crate_.clone().versions(db).await?;

    // returning a Response instead of Json here so we don't have to close
    // every Crate/CrateVersion etc, would be easier if we just had an owned
    // version of each but we're using `spawn_blocking` in chartered-db for
    // diesel which requires `'static' which basically forces us to use Arc
    // if we want to keep a reference to anything ourselves.
    Ok(Json(Response {
        info: crate_.as_ref().into(),
        versions: versions
            .into_iter()
            .map(|v| {
                let (inner, meta) = v.into_cargo_format(&crate_);
                ResponseVersion {
                    inner: inner.into_owned(),
                    meta,
                }
            })
            .map(|v| v.into_cargo_format(&crate_))
            .collect(),
    }))
    })
    .into_response())
}

#[derive(Serialize)]
pub struct ResponseVersion {
pub struct Response<'a> {
    #[serde(flatten)]
    meta: CrateVersionMetadata,
    #[serde(flatten)]
    inner: CrateVersion<'static>,
    info: ResponseInfo<'a>,
    versions: Vec<CrateVersion<'a>>,
}

#[derive(Serialize)]
pub struct Response {
    versions: Vec<ResponseVersion>,
pub struct ResponseInfo<'a> {
    name: &'a str,
    readme: Option<&'a str>,
    description: Option<&'a str>,
    repository: Option<&'a str>,
    homepage: Option<&'a str>,
    documentation: Option<&'a str>,
}

impl<'a> From<&'a Crate> for ResponseInfo<'a> {
    fn from(crate_: &'a Crate) -> Self {
        Self {
            name: &crate_.name,
            readme: crate_.readme.as_deref(),
            description: crate_.description.as_deref(),
            repository: crate_.repository.as_deref(),
            homepage: crate_.homepage.as_deref(),
            documentation: crate_.documentation.as_deref(),
        }
    }
}