use kellnr_common::index_metadata::IndexMetadata;
use kellnr_common::normalized_name::NormalizedName;
use kellnr_common::publish_metadata::PublishMetadata;
use kellnr_entity::{
auth_token, crate_author, crate_author_to_crate, crate_category, crate_category_to_crate,
crate_index, crate_keyword, crate_keyword_to_crate, crate_meta, cratesio_crate, cratesio_index,
krate, owner, user,
};
use sea_orm::{
ActiveModelTrait, ActiveValue, ColumnTrait, ConnectionTrait, EntityTrait, QueryFilter, Set,
};
use crate::ConString;
use crate::error::DbError;
use crate::password::{hash_pwd, hash_token};
use crate::provider::DbResult;
pub async fn get_desc_for_crate_dep<C: ConnectionTrait>(
db_con: &C,
name: &str,
registry: Option<&str>,
) -> DbResult<Option<String>> {
let desc = if registry.unwrap_or_default() == "https://github.com/rust-lang/crates.io-index" {
let krate = cratesio_crate::Entity::find()
.filter(cratesio_crate::Column::Name.eq(name))
.one(db_con)
.await?;
krate.and_then(|krate| krate.description)
} else {
let krate = krate::Entity::find()
.filter(krate::Column::Name.eq(name))
.one(db_con)
.await?;
krate.and_then(|krate| krate.description)
};
Ok(desc)
}
pub async fn insert_admin_credentials<C: ConnectionTrait>(
db_con: &C,
con_string: &ConString,
) -> DbResult<()> {
let hashed_pwd = hash_pwd(&con_string.admin_pwd(), &con_string.salt());
let admin = user::ActiveModel {
name: Set("admin".to_string()),
pwd: Set(hashed_pwd),
salt: Set(con_string.salt()),
is_admin: Set(true),
is_read_only: Set(false),
..Default::default()
};
let res: sea_orm::InsertResult<user::ActiveModel> =
user::Entity::insert(admin).exec(db_con).await?;
if let Some(token) = &con_string.admin_token() {
let auth_token_hash = hash_token(token);
let auth_token = auth_token::ActiveModel {
name: Set("admin".to_string()),
token: Set(auth_token_hash),
user_fk: Set(res.last_insert_id),
..Default::default()
};
auth_token::Entity::insert(auth_token).exec(db_con).await?;
}
Ok(())
}
pub async fn no_user_exists<C: ConnectionTrait>(db_con: &C) -> DbResult<bool> {
let id = user::Entity::find()
.one(db_con)
.await?
.map(|model| model.id);
Ok(id.is_none())
}
pub async fn add_owner_if_not_exists<C: ConnectionTrait>(
db_con: &C,
owner_name: &str,
crate_id: i64,
) -> DbResult<()> {
let user_fk = user::Entity::find()
.filter(user::Column::Name.eq(owner_name))
.one(db_con)
.await?
.map(|model| model.id)
.ok_or_else(|| DbError::UserNotFound(owner_name.to_string()))?;
let existing_owner = owner::Entity::find()
.filter(owner::Column::CrateFk.eq(crate_id))
.filter(owner::Column::UserFk.eq(user_fk))
.one(db_con)
.await?;
if existing_owner.is_none() {
let o = owner::ActiveModel {
user_fk: Set(user_fk),
crate_fk: Set(crate_id),
..Default::default()
};
o.insert(db_con).await?;
}
Ok(())
}
pub async fn add_crate_index<C: ConnectionTrait>(
db_con: &C,
pub_metadata: &PublishMetadata,
cksum: &str,
crate_id: i64,
) -> DbResult<()> {
let index_data = IndexMetadata::from_reg_meta(pub_metadata, cksum);
let deps = if index_data.deps.is_empty() {
None
} else {
let deps = serde_json::to_value(&index_data.deps)
.map_err(|e| DbError::FailedToConvertToJson(e.to_string()))?;
Some(deps)
};
let features = serde_json::to_value(&index_data.features)
.map_err(|e| DbError::FailedToConvertToJson(e.to_string()))?;
let ci = crate_index::ActiveModel {
id: ActiveValue::default(),
name: Set(index_data.name),
vers: Set(index_data.vers),
deps: Set(deps),
cksum: Set(cksum.to_owned()),
features: Set(Some(features)),
yanked: ActiveValue::default(),
pubtime: Set(index_data.pubtime.map(|dt| dt.naive_utc())),
links: Set(index_data.links),
v: Set(index_data.v.unwrap_or(1) as i32),
crate_fk: Set(crate_id),
};
ci.insert(db_con).await?;
Ok(())
}
pub async fn add_crate_metadata<C: ConnectionTrait>(
db_con: &C,
pub_metadata: &PublishMetadata,
created: &str,
crate_id: i64,
) -> DbResult<()> {
let cm = crate_meta::ActiveModel {
id: ActiveValue::default(),
version: Set(pub_metadata.vers.clone()),
created: Set(created.to_string()),
downloads: Set(0),
crate_fk: Set(crate_id),
readme: Set(pub_metadata.readme.clone()),
license: Set(pub_metadata.license.clone()),
license_file: Set(pub_metadata.license_file.clone()),
documentation: Set(pub_metadata.documentation.clone()),
};
cm.insert(db_con).await?;
Ok(())
}
pub async fn update_crate_categories<C: ConnectionTrait>(
db_con: &C,
pub_metadata: &PublishMetadata,
crate_id: i64,
) -> DbResult<()> {
let categories = pub_metadata.categories.clone();
crate_category_to_crate::Entity::delete_many()
.filter(crate_category_to_crate::Column::CrateFk.eq(crate_id))
.exec(db_con)
.await?;
for category in categories {
let category_fk = crate_category::Entity::find()
.filter(crate_category::Column::Category.eq(&category))
.one(db_con)
.await?
.map(|model| model.id);
let category_fk = if let Some(category_fk) = category_fk {
category_fk
} else {
let cc = crate_category::ActiveModel {
id: ActiveValue::default(),
category: Set(category),
};
cc.insert(db_con).await?.id
};
let cctc = crate_category_to_crate::ActiveModel {
id: ActiveValue::default(),
crate_fk: Set(crate_id),
category_fk: Set(category_fk),
};
cctc.insert(db_con).await?;
}
Ok(())
}
pub async fn update_crate_keywords<C: ConnectionTrait>(
db_con: &C,
pub_metadata: &PublishMetadata,
crate_id: i64,
) -> DbResult<()> {
let keywords = pub_metadata.keywords.clone();
crate_keyword_to_crate::Entity::delete_many()
.filter(crate_keyword_to_crate::Column::CrateFk.eq(crate_id))
.exec(db_con)
.await?;
for keyword in keywords {
let keyword_fk = crate_keyword::Entity::find()
.filter(crate_keyword::Column::Keyword.eq(&keyword))
.one(db_con)
.await?
.map(|model| model.id);
let keyword_fk = if let Some(keyword_fk) = keyword_fk {
keyword_fk
} else {
let ck = crate_keyword::ActiveModel {
id: ActiveValue::default(),
keyword: Set(keyword),
};
ck.insert(db_con).await?.id
};
let cktc = crate_keyword_to_crate::ActiveModel {
id: ActiveValue::default(),
crate_fk: Set(crate_id),
keyword_fk: Set(keyword_fk),
};
cktc.insert(db_con).await?;
}
Ok(())
}
pub async fn update_crate_authors<C: ConnectionTrait>(
db_con: &C,
pub_metadata: &PublishMetadata,
crate_id: i64,
) -> DbResult<()> {
let authors = pub_metadata.authors.clone().unwrap_or_default();
crate_author_to_crate::Entity::delete_many()
.filter(crate_author_to_crate::Column::CrateFk.eq(crate_id))
.exec(db_con)
.await?;
for author in authors {
let author_fk = crate_author::Entity::find()
.filter(crate_author::Column::Author.eq(&author))
.one(db_con)
.await?
.map(|model| model.id);
let author_fk = if let Some(author_fk) = author_fk {
author_fk
} else {
let ca = crate_author::ActiveModel {
id: ActiveValue::default(),
author: Set(author),
};
ca.insert(db_con).await?.id
};
let catc = crate_author_to_crate::ActiveModel {
id: ActiveValue::default(),
crate_fk: Set(crate_id),
author_fk: Set(author_fk),
};
catc.insert(db_con).await?;
}
Ok(())
}
pub async fn compute_etag<C: ConnectionTrait>(
db_con: &C,
crate_name: &str,
crate_id: i64,
) -> DbResult<String> {
let crate_indices = crate_index::Entity::find()
.filter(crate_index::Column::CrateFk.eq(crate_id))
.all(db_con)
.await?;
let index_metadata = crate_index_model_to_index_metadata(crate_name, crate_indices)?;
let data = index_metadata_to_bytes(&index_metadata)?;
Ok(sha256::digest(data))
}
pub fn index_metadata_to_bytes(index_metadata: &[IndexMetadata]) -> DbResult<Vec<u8>> {
IndexMetadata::serialize_indices(index_metadata)
.map(String::into_bytes)
.map_err(|e| DbError::FailedToConvertToJson(format!("{e}")))
}
pub fn crate_index_model_to_index_metadata(
crate_name: &str,
crate_indices: Vec<crate_index::Model>,
) -> DbResult<Vec<IndexMetadata>> {
let mut index_metadata = vec![];
for ci in crate_indices {
let deps = match ci.deps {
Some(deps) => serde_json::value::from_value(deps).map_err(|e| {
DbError::FailedToConvertFromJson(format!(
"Failed to deserialize crate dependencies of {crate_name}: {e}"
))
})?,
None => vec![],
};
let features = ci.features.clone().unwrap_or_default();
let features = serde_json::value::from_value(features).map_err(|e| {
DbError::FailedToConvertFromJson(format!(
"Failed to deserialize crate features of {crate_name}: {e}"
))
})?;
let cm = IndexMetadata {
name: ci.name,
vers: ci.vers,
deps,
cksum: ci.cksum,
features,
yanked: ci.yanked,
pubtime: ci.pubtime.map(|dt| dt.and_utc()),
links: ci.links,
v: Some(ci.v as u32),
features2: None,
};
index_metadata.push(cm);
}
Ok(index_metadata)
}
pub fn cratesio_index_model_to_index_metadata(
crate_name: &NormalizedName,
crate_indices: Vec<cratesio_index::Model>,
) -> DbResult<Vec<IndexMetadata>> {
let mut index_metadata = vec![];
for ci in crate_indices {
let deps = match ci.deps {
Some(deps) => serde_json::value::from_value(deps).map_err(|e| {
DbError::FailedToConvertFromJson(format!(
"Failed to deserialize crate dependencies of {crate_name}: {e}"
))
})?,
None => vec![],
};
let features = ci.features.clone().unwrap_or_default();
let features = serde_json::value::from_value(features).map_err(|e| {
DbError::FailedToConvertFromJson(format!(
"Failed to deserialize crate features of {crate_name}: {e}"
))
})?;
let features2 = ci.features2.clone().unwrap_or_default();
let features2 = serde_json::value::from_value(features2).map_err(|e| {
DbError::FailedToConvertFromJson(format!(
"Failed to deserialize crate features of {crate_name}: {e}"
))
})?;
let cm = IndexMetadata {
name: ci.name,
vers: ci.vers.clone(),
deps,
cksum: ci.cksum.clone(),
pubtime: ci.pubtime.map(|dt| dt.and_utc()),
features,
features2,
yanked: ci.yanked,
links: ci.links.clone(),
v: Some(ci.v as u32),
};
index_metadata.push(cm);
}
Ok(index_metadata)
}
pub async fn update_etag<C: ConnectionTrait>(
db_con: &C,
crate_name: &str,
crate_id: i64,
) -> DbResult<()> {
let etag = compute_etag(db_con, crate_name, crate_id).await?;
let mut krate: krate::ActiveModel = krate::Entity::find()
.filter(krate::Column::Id.eq(crate_id))
.one(db_con)
.await?
.ok_or_else(|| DbError::CrateNotFound(crate_name.to_string()))?
.into();
krate.e_tag = Set(etag);
krate.update(db_con).await?;
Ok(())
}
pub async fn get_max_version_from_id<C: ConnectionTrait>(
db_con: &C,
crate_id: i64,
) -> DbResult<kellnr_common::version::Version> {
let krate = krate::Entity::find_by_id(crate_id).one(db_con).await?;
let k = krate.ok_or(DbError::FailedToGetMaxVersionById(crate_id))?;
let v = kellnr_common::version::Version::try_from(&k.max_version)
.map_err(|_| DbError::FailedToGetMaxVersionById(crate_id))?;
Ok(v)
}