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(-)
@@ -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,
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 {
@@ -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>,
}
}
@@ -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,
@@ -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,
@@ -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 }) {
@@ -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?;
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(),
}
}
}