diff options
Diffstat (limited to 'rust/src')
| -rw-r--r-- | rust/src/bin/fatcatd.rs | 1 | ||||
| -rw-r--r-- | rust/src/database_models.rs | 131 | ||||
| -rw-r--r-- | rust/src/database_schema.rs | 33 | ||||
| -rw-r--r-- | rust/src/editing.rs | 6 | ||||
| -rw-r--r-- | rust/src/endpoint_handlers.rs | 156 | ||||
| -rw-r--r-- | rust/src/endpoints.rs | 26 | ||||
| -rw-r--r-- | rust/src/entity_crud.rs | 175 | ||||
| -rw-r--r-- | rust/src/lib.rs | 2 | 
8 files changed, 373 insertions, 157 deletions
| diff --git a/rust/src/bin/fatcatd.rs b/rust/src/bin/fatcatd.rs index 75a6f000..ccce6725 100644 --- a/rust/src/bin/fatcatd.rs +++ b/rust/src/bin/fatcatd.rs @@ -88,7 +88,6 @@ fn main() -> Result<()> {              server.metrics.incr("restart").unwrap();          }      }; -    info!(logger, "{:#?}", server.metrics);      info!(          logger, diff --git a/rust/src/database_models.rs b/rust/src/database_models.rs index 63fbcb29..adb38bda 100644 --- a/rust/src/database_models.rs +++ b/rust/src/database_models.rs @@ -3,8 +3,10 @@  use crate::database_schema::*;  use crate::errors::*;  use crate::identifiers::uuid2fcid; -use chrono; -use fatcat_api_spec::models::{ChangelogEntry, Editgroup, EditgroupAnnotation, Editor, EntityEdit}; +use chrono::Utc; +use fatcat_api_spec::models::{ +    ChangelogEntry, Editgroup, EditgroupAnnotation, Editor, EntityEdit, ReleaseRef, +};  use serde_json;  use uuid::Uuid; @@ -127,11 +129,10 @@ pub struct ContainerRevRow {      pub id: Uuid,      pub extra_json: Option<serde_json::Value>,      pub name: String, +    pub container_type: Option<String>,      pub publisher: Option<String>,      pub issnl: Option<String>,      pub wikidata_qid: Option<String>, -    pub abbrev: Option<String>, -    pub coden: Option<String>,  }  #[derive(Debug, Associations, AsChangeset, Insertable)] @@ -139,11 +140,10 @@ pub struct ContainerRevRow {  pub struct ContainerRevNewRow {      pub extra_json: Option<serde_json::Value>,      pub name: String, +    pub container_type: Option<String>,      pub publisher: Option<String>,      pub issnl: Option<String>,      pub wikidata_qid: Option<String>, -    pub abbrev: Option<String>, -    pub coden: Option<String>,  }  entity_structs!( @@ -305,7 +305,7 @@ pub struct WebcaptureRevCdxRow {      pub id: i64,      pub webcapture_rev: Uuid,      pub surt: String, -    pub timestamp: String, +    pub timestamp: chrono::DateTime<Utc>,      pub url: String,      pub mimetype: Option<String>,      pub status_code: Option<i64>, @@ -318,7 +318,7 @@ pub struct WebcaptureRevCdxRow {  pub struct WebcaptureRevCdxNewRow {      pub webcapture_rev: Uuid,      pub surt: String, -    pub timestamp: String, +    pub timestamp: chrono::DateTime<Utc>,      pub url: String,      pub mimetype: Option<String>,      pub status_code: Option<i64>, @@ -376,7 +376,9 @@ pub struct ReleaseRevRow {      pub extra_json: Option<serde_json::Value>,      pub work_ident_id: Uuid,      pub container_ident_id: Option<Uuid>, +    pub refs_blob_sha1: Option<String>,      pub title: String, +    pub original_title: Option<String>,      pub release_type: Option<String>,      pub release_status: Option<String>,      pub release_date: Option<chrono::NaiveDate>, @@ -387,11 +389,14 @@ pub struct ReleaseRevRow {      pub wikidata_qid: Option<String>,      pub isbn13: Option<String>,      pub core_id: Option<String>, +    pub arxiv_id: Option<String>, +    pub jstor_id: Option<String>,      pub volume: Option<String>,      pub issue: Option<String>,      pub pages: Option<String>,      pub publisher: Option<String>,      pub language: Option<String>, +    pub license_slug: Option<String>,  }  #[derive(Debug, Associations, AsChangeset, Insertable)] @@ -400,7 +405,9 @@ pub struct ReleaseRevNewRow {      pub extra_json: Option<serde_json::Value>,      pub work_ident_id: Uuid,      pub container_ident_id: Option<Uuid>, +    pub refs_blob_sha1: Option<String>,      pub title: String, +    pub original_title: Option<String>,      pub release_type: Option<String>,      pub release_status: Option<String>,      pub release_date: Option<chrono::NaiveDate>, @@ -411,11 +418,14 @@ pub struct ReleaseRevNewRow {      pub wikidata_qid: Option<String>,      pub isbn13: Option<String>,      pub core_id: Option<String>, +    pub arxiv_id: Option<String>, +    pub jstor_id: Option<String>,      pub volume: Option<String>,      pub issue: Option<String>,      pub pages: Option<String>,      pub publisher: Option<String>,      pub language: Option<String>, +    pub license_slug: Option<String>,  }  entity_structs!( @@ -476,6 +486,7 @@ pub struct ReleaseContribRow {      pub creator_ident_id: Option<Uuid>,      pub raw_name: Option<String>,      pub role: Option<String>, +    pub raw_affiliation: Option<String>,      pub index_val: Option<i32>,      pub extra_json: Option<serde_json::Value>,  } @@ -487,39 +498,107 @@ pub struct ReleaseContribNewRow {      pub creator_ident_id: Option<Uuid>,      pub raw_name: Option<String>,      pub role: Option<String>, +    pub raw_affiliation: Option<String>,      pub index_val: Option<i32>,      pub extra_json: Option<serde_json::Value>,  } -#[derive(Debug, Queryable, Identifiable, Associations, AsChangeset)] +#[derive(Debug, Queryable, Insertable, Associations, AsChangeset)]  #[table_name = "release_ref"]  pub struct ReleaseRefRow { -    pub id: i64,      pub release_rev: Uuid, -    pub target_release_ident_id: Option<Uuid>, -    pub index_val: Option<i32>, -    pub key: Option<String>, -    pub extra_json: Option<serde_json::Value>, -    pub container_name: Option<String>, -    pub year: Option<i32>, -    pub title: Option<String>, -    pub locator: Option<String>, +    pub index_val: i32, +    pub target_release_ident_id: Uuid,  } -#[derive(Debug, Insertable, AsChangeset)] -#[table_name = "release_ref"] -pub struct ReleaseRefNewRow { -    pub release_rev: Uuid, -    pub target_release_ident_id: Option<Uuid>, -    pub index_val: Option<i32>, +#[derive(Debug, Queryable, Insertable, Associations, AsChangeset)] +#[table_name = "refs_blob"] +pub struct RefsBlobRow { +    pub sha1: String, +    pub refs_json: serde_json::Value, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +/// This model is a stable representation of what goes in a RefsBlobRow `refs_json` field (an array +/// of this model). We could rely on the `ReleaseRef` API spec model directly, but that would lock +/// the database contents to the API spec rigidly; by defining this struct independently, we can +/// migrate the schemas. To start, this is a direct copy of the `ReleaseRef` model. +pub struct RefsBlobJson { +    #[serde(rename = "index")] +    #[serde(skip_serializing_if = "Option::is_none")] +    pub index: Option<i64>, + +    /// base32-encoded unique identifier +    #[serde(rename = "target_release_id")] +    #[serde(skip_serializing_if = "Option::is_none")] +    pub target_release_id: Option<String>, + +    #[serde(rename = "extra")] +    #[serde(skip_serializing_if = "Option::is_none")] +    pub extra: Option<serde_json::Value>, + +    #[serde(rename = "key")] +    #[serde(skip_serializing_if = "Option::is_none")]      pub key: Option<String>, -    pub extra_json: Option<serde_json::Value>, + +    #[serde(rename = "year")] +    #[serde(skip_serializing_if = "Option::is_none")] +    pub year: Option<i64>, + +    #[serde(rename = "container_name")] +    #[serde(skip_serializing_if = "Option::is_none")]      pub container_name: Option<String>, -    pub year: Option<i32>, + +    #[serde(rename = "title")] +    #[serde(skip_serializing_if = "Option::is_none")]      pub title: Option<String>, + +    #[serde(rename = "locator")] +    #[serde(skip_serializing_if = "Option::is_none")]      pub locator: Option<String>,  } +impl RefsBlobJson { +    pub fn into_model(self) -> ReleaseRef { +        ReleaseRef { +            index: self.index, +            target_release_id: self.target_release_id, +            extra: self.extra, +            key: self.key, +            year: self.year, +            container_name: self.container_name, +            title: self.title, +            locator: self.locator, +        } +    } + +    pub fn to_model(&self) -> ReleaseRef { +        ReleaseRef { +            index: self.index, +            target_release_id: self.target_release_id.clone(), +            extra: self.extra.clone(), +            key: self.key.clone(), +            year: self.year, +            container_name: self.container_name.clone(), +            title: self.title.clone(), +            locator: self.locator.clone(), +        } +    } + +    pub fn from_model(model: &ReleaseRef) -> RefsBlobJson { +        RefsBlobJson { +            index: model.index, +            target_release_id: model.target_release_id.clone(), +            extra: model.extra.clone(), +            key: model.key.clone(), +            year: model.year, +            container_name: model.container_name.clone(), +            title: model.title.clone(), +            locator: model.locator.clone(), +        } +    } +} +  #[derive(Debug, Queryable, Insertable, Associations, AsChangeset)]  #[table_name = "file_rev_release"]  pub struct FileRevReleaseRow { diff --git a/rust/src/database_schema.rs b/rust/src/database_schema.rs index 3bc57d95..ea184226 100644 --- a/rust/src/database_schema.rs +++ b/rust/src/database_schema.rs @@ -51,11 +51,10 @@ table! {          id -> Uuid,          extra_json -> Nullable<Jsonb>,          name -> Text, +        container_type -> Nullable<Text>,          publisher -> Nullable<Text>,          issnl -> Nullable<Text>,          wikidata_qid -> Nullable<Text>, -        abbrev -> Nullable<Text>, -        coden -> Nullable<Text>,      }  } @@ -239,12 +238,20 @@ table! {  }  table! { +    refs_blob (sha1) { +        sha1 -> Text, +        refs_json -> Jsonb, +    } +} + +table! {      release_contrib (id) {          id -> Int8,          release_rev -> Uuid,          creator_ident_id -> Nullable<Uuid>,          raw_name -> Nullable<Text>,          role -> Nullable<Text>, +        raw_affiliation -> Nullable<Text>,          index_val -> Nullable<Int4>,          extra_json -> Nullable<Jsonb>,      } @@ -273,17 +280,10 @@ table! {  }  table! { -    release_ref (id) { -        id -> Int8, +    release_ref (release_rev, index_val) {          release_rev -> Uuid, -        target_release_ident_id -> Nullable<Uuid>, -        index_val -> Nullable<Int4>, -        key -> Nullable<Text>, -        extra_json -> Nullable<Jsonb>, -        container_name -> Nullable<Text>, -        year -> Nullable<Int4>, -        title -> Nullable<Text>, -        locator -> Nullable<Text>, +        index_val -> Int4, +        target_release_ident_id -> Uuid,      }  } @@ -293,7 +293,9 @@ table! {          extra_json -> Nullable<Jsonb>,          work_ident_id -> Uuid,          container_ident_id -> Nullable<Uuid>, +        refs_blob_sha1 -> Nullable<Text>,          title -> Text, +        original_title -> Nullable<Text>,          release_type -> Nullable<Text>,          release_status -> Nullable<Text>,          release_date -> Nullable<Date>, @@ -304,11 +306,14 @@ table! {          wikidata_qid -> Nullable<Text>,          isbn13 -> Nullable<Text>,          core_id -> Nullable<Text>, +        arxiv_id -> Nullable<Text>, +        jstor_id -> Nullable<Text>,          volume -> Nullable<Text>,          issue -> Nullable<Text>,          pages -> Nullable<Text>,          publisher -> Nullable<Text>,          language -> Nullable<Text>, +        license_slug -> Nullable<Text>,      }  } @@ -358,7 +363,7 @@ table! {          id -> Int8,          webcapture_rev -> Uuid,          surt -> Text, -        timestamp -> Text, +        timestamp -> Timestamptz,          url -> Text,          mimetype -> Nullable<Text>,          status_code -> Nullable<Int8>, @@ -439,6 +444,7 @@ joinable!(release_ident -> release_rev (rev_id));  joinable!(release_ref -> release_ident (target_release_ident_id));  joinable!(release_ref -> release_rev (release_rev));  joinable!(release_rev -> container_ident (container_ident_id)); +joinable!(release_rev -> refs_blob (refs_blob_sha1));  joinable!(release_rev -> work_ident (work_ident_id));  joinable!(release_rev_abstract -> abstracts (abstract_sha1));  joinable!(release_rev_abstract -> release_rev (release_rev)); @@ -475,6 +481,7 @@ allow_tables_to_appear_in_same_query!(      fileset_rev_file,      fileset_rev_release,      fileset_rev_url, +    refs_blob,      release_contrib,      release_edit,      release_ident, diff --git a/rust/src/editing.rs b/rust/src/editing.rs index e181e8a7..c17e5964 100644 --- a/rust/src/editing.rs +++ b/rust/src/editing.rs @@ -42,6 +42,8 @@ pub fn make_edit_context(      editor_id: FatcatId,      editgroup_id: Option<FatcatId>,      autoaccept: bool, +    description: Option<String>, +    extra: Option<serde_json::Value>,  ) -> Result<EditContext> {      // *either* autoaccept is false and editgroup_id is Some, *or* autoaccept is true and      // editgroup_id is None @@ -54,8 +56,8 @@ pub fn make_edit_context(                  editor: None,                  changelog_index: None,                  submitted: None, -                description: None, -                extra: None, +                description: description, +                extra: extra,                  annotations: None,                  edits: None,              }; diff --git a/rust/src/endpoint_handlers.rs b/rust/src/endpoint_handlers.rs index bc606af9..d9bd3403 100644 --- a/rust/src/endpoint_handlers.rs +++ b/rust/src/endpoint_handlers.rs @@ -26,9 +26,11 @@ macro_rules! entity_batch_handler {              autoaccept: bool,              editor_id: FatcatId,              editgroup_id: Option<FatcatId>, +            description: Option<String>, +            extra: Option<serde_json::Value>,          ) -> Result<Vec<EntityEdit>> { -            let edit_context = make_edit_context(conn, editor_id, editgroup_id, autoaccept)?; +            let edit_context = make_edit_context(conn, editor_id, editgroup_id, autoaccept, description, extra)?;              edit_context.check(&conn)?;              let model_list: Vec<&models::$model> = entity_list.iter().map(|e| e).collect();              let edits = $model::db_create_batch(conn, &edit_context, model_list.as_slice())?; @@ -259,71 +261,99 @@ impl Server {          pmid: &Option<String>,          pmcid: &Option<String>,          core_id: &Option<String>, +        arxiv_id: &Option<String>, +        jstor_id: &Option<String>,          expand_flags: ExpandFlags,          hide_flags: HideFlags,      ) -> Result<ReleaseEntity> { -        let (ident, rev): (ReleaseIdentRow, ReleaseRevRow) = -            match (doi, wikidata_qid, isbn13, pmid, pmcid, core_id) { -                (Some(doi), None, None, None, None, None) => { -                    check_doi(doi)?; -                    release_ident::table -                        .inner_join(release_rev::table) -                        .filter(release_rev::doi.eq(doi)) -                        .filter(release_ident::is_live.eq(true)) -                        .filter(release_ident::redirect_id.is_null()) -                        .first(conn)? -                } -                (None, Some(wikidata_qid), None, None, None, None) => { -                    check_wikidata_qid(wikidata_qid)?; -                    release_ident::table -                        .inner_join(release_rev::table) -                        .filter(release_rev::wikidata_qid.eq(wikidata_qid)) -                        .filter(release_ident::is_live.eq(true)) -                        .filter(release_ident::redirect_id.is_null()) -                        .first(conn)? -                } -                (None, None, Some(isbn13), None, None, None) => { -                    // TODO: check_isbn13(isbn13)?; -                    release_ident::table -                        .inner_join(release_rev::table) -                        .filter(release_rev::isbn13.eq(isbn13)) -                        .filter(release_ident::is_live.eq(true)) -                        .filter(release_ident::redirect_id.is_null()) -                        .first(conn)? -                } -                (None, None, None, Some(pmid), None, None) => { -                    check_pmid(pmid)?; -                    release_ident::table -                        .inner_join(release_rev::table) -                        .filter(release_rev::pmid.eq(pmid)) -                        .filter(release_ident::is_live.eq(true)) -                        .filter(release_ident::redirect_id.is_null()) -                        .first(conn)? -                } -                (None, None, None, None, Some(pmcid), None) => { -                    check_pmcid(pmcid)?; -                    release_ident::table -                        .inner_join(release_rev::table) -                        .filter(release_rev::pmcid.eq(pmcid)) -                        .filter(release_ident::is_live.eq(true)) -                        .filter(release_ident::redirect_id.is_null()) -                        .first(conn)? -                } -                (None, None, None, None, None, Some(core_id)) => { -                    // TODO: check_core_id(core_id)?; -                    release_ident::table -                        .inner_join(release_rev::table) -                        .filter(release_rev::core_id.eq(core_id)) -                        .filter(release_ident::is_live.eq(true)) -                        .filter(release_ident::redirect_id.is_null()) -                        .first(conn)? -                } -                _ => { -                    return Err( -                        FatcatError::MissingOrMultipleExternalId("in lookup".to_string()).into(), -                    ); -                } -            }; +        let (ident, rev): (ReleaseIdentRow, ReleaseRevRow) = match ( +            doi, +            wikidata_qid, +            isbn13, +            pmid, +            pmcid, +            core_id, +            arxiv_id, +            jstor_id, +        ) { +            (Some(doi), None, None, None, None, None, None, None) => { +                check_doi(doi)?; +                release_ident::table +                    .inner_join(release_rev::table) +                    .filter(release_rev::doi.eq(doi)) +                    .filter(release_ident::is_live.eq(true)) +                    .filter(release_ident::redirect_id.is_null()) +                    .first(conn)? +            } +            (None, Some(wikidata_qid), None, None, None, None, None, None) => { +                check_wikidata_qid(wikidata_qid)?; +                release_ident::table +                    .inner_join(release_rev::table) +                    .filter(release_rev::wikidata_qid.eq(wikidata_qid)) +                    .filter(release_ident::is_live.eq(true)) +                    .filter(release_ident::redirect_id.is_null()) +                    .first(conn)? +            } +            (None, None, Some(isbn13), None, None, None, None, None) => { +                // TODO: check_isbn13(isbn13)?; +                release_ident::table +                    .inner_join(release_rev::table) +                    .filter(release_rev::isbn13.eq(isbn13)) +                    .filter(release_ident::is_live.eq(true)) +                    .filter(release_ident::redirect_id.is_null()) +                    .first(conn)? +            } +            (None, None, None, Some(pmid), None, None, None, None) => { +                check_pmid(pmid)?; +                release_ident::table +                    .inner_join(release_rev::table) +                    .filter(release_rev::pmid.eq(pmid)) +                    .filter(release_ident::is_live.eq(true)) +                    .filter(release_ident::redirect_id.is_null()) +                    .first(conn)? +            } +            (None, None, None, None, Some(pmcid), None, None, None) => { +                check_pmcid(pmcid)?; +                release_ident::table +                    .inner_join(release_rev::table) +                    .filter(release_rev::pmcid.eq(pmcid)) +                    .filter(release_ident::is_live.eq(true)) +                    .filter(release_ident::redirect_id.is_null()) +                    .first(conn)? +            } +            (None, None, None, None, None, Some(core_id), None, None) => { +                // TODO: check_core_id(core_id)?; +                release_ident::table +                    .inner_join(release_rev::table) +                    .filter(release_rev::core_id.eq(core_id)) +                    .filter(release_ident::is_live.eq(true)) +                    .filter(release_ident::redirect_id.is_null()) +                    .first(conn)? +            } +            (None, None, None, None, None, None, Some(arxiv_id), None) => { +                // TODO: check_arxiv_id(arxiv_id)?; +                release_ident::table +                    .inner_join(release_rev::table) +                    .filter(release_rev::arxiv_id.eq(arxiv_id)) +                    .filter(release_ident::is_live.eq(true)) +                    .filter(release_ident::redirect_id.is_null()) +                    .first(conn)? +            } +            (None, None, None, None, None, None, None, Some(jstor_id)) => { +                // TODO: check_jstor_id(jstor_id)?; +                release_ident::table +                    .inner_join(release_rev::table) +                    .filter(release_rev::jstor_id.eq(jstor_id)) +                    .filter(release_ident::is_live.eq(true)) +                    .filter(release_ident::redirect_id.is_null()) +                    .first(conn)? +            } +            _ => { +                return Err( +                    FatcatError::MissingOrMultipleExternalId("in lookup".to_string()).into(), +                ); +            } +        };          let mut entity = ReleaseEntity::db_from_row(conn, rev, Some(ident), hide_flags)?;          entity.db_expand(&conn, expand_flags)?; diff --git a/rust/src/endpoints.rs b/rust/src/endpoints.rs index f7e93448..2e467957 100644 --- a/rust/src/endpoints.rs +++ b/rust/src/endpoints.rs @@ -120,7 +120,7 @@ macro_rules! wrap_entity_handlers {                  let auth_context = self.auth_confectionary.require_auth(&conn, &context.auth_data, Some(stringify!($post_fn)))?;                  auth_context.require_role(FatcatRole::Editor)?;                  auth_context.require_editgroup(&conn, editgroup_id)?; -                let edit_context = make_edit_context(&conn, auth_context.editor_id, Some(editgroup_id), false)?; +                let edit_context = make_edit_context(&conn, auth_context.editor_id, Some(editgroup_id), false, None, None)?;                  edit_context.check(&conn)?;                  entity.db_create(&conn, &edit_context)?.into_model()              }).map_err(|e| FatcatError::from(e)) { @@ -138,18 +138,30 @@ macro_rules! wrap_entity_handlers {              entity_list: &Vec<models::$model>,              autoaccept: Option<bool>,              editgroup_id: Option<String>, +            description: Option<String>, +            extra: Option<String>,              context: &Context,          ) -> Box<Future<Item = $post_batch_resp, Error = ApiError> + Send> {              let conn = self.db_pool.get().expect("db_pool error");              let ret = match conn.transaction(|| {                  let auth_context = self.auth_confectionary.require_auth(&conn, &context.auth_data, Some(stringify!($post_batch_fn)))?; -                auth_context.require_role(FatcatRole::Editor)?; +                let autoaccept = autoaccept.unwrap_or(false); +                if autoaccept { +                    auth_context.require_role(FatcatRole::Admin)?; +                } else { +                    auth_context.require_role(FatcatRole::Editor)?; +                };                  let editgroup_id = if let Some(s) = editgroup_id { +                    // make_edit_context() checks for "both editgroup_id and autosubmit" error case                      let eg_id = FatcatId::from_str(&s)?;                      auth_context.require_editgroup(&conn, eg_id)?;                      Some(eg_id)                  } else { None }; -                self.$post_batch_handler(&conn, entity_list, autoaccept.unwrap_or(false), auth_context.editor_id, editgroup_id) +                let extra: Option<serde_json::Value> = match extra { +                    Some(v) => serde_json::from_str(&v)?, +                    None => None, +                }; +                self.$post_batch_handler(&conn, entity_list, autoaccept, auth_context.editor_id, editgroup_id, description, extra)              }).map_err(|e| FatcatError::from(e)) {                  Ok(edits) => {                      self.metrics.count("entities.created", edits.len() as i64).ok(); @@ -178,7 +190,7 @@ macro_rules! wrap_entity_handlers {                  auth_context.require_role(FatcatRole::Editor)?;                  let entity_id = FatcatId::from_str(&ident)?;                  auth_context.require_editgroup(&conn, editgroup_id)?; -                let edit_context = make_edit_context(&conn, auth_context.editor_id, Some(editgroup_id), false)?; +                let edit_context = make_edit_context(&conn, auth_context.editor_id, Some(editgroup_id), false, None, None)?;                  edit_context.check(&conn)?;                  entity.db_update(&conn, &edit_context, entity_id)?.into_model()              }).map_err(|e| FatcatError::from(e)) { @@ -204,7 +216,7 @@ macro_rules! wrap_entity_handlers {                  auth_context.require_role(FatcatRole::Editor)?;                  let entity_id = FatcatId::from_str(&ident)?;                  auth_context.require_editgroup(&conn, editgroup_id)?; -                let edit_context = make_edit_context(&conn, auth_context.editor_id, Some(editgroup_id), false)?; +                let edit_context = make_edit_context(&conn, auth_context.editor_id, Some(editgroup_id), false, None, None)?;                  edit_context.check(&conn)?;                  $model::db_delete(&conn, &edit_context, entity_id)?.into_model()              }).map_err(|e| FatcatError::from(e)) { @@ -659,6 +671,8 @@ impl Api for Server {          pmid: Option<String>,          pmcid: Option<String>,          core_id: Option<String>, +        arxiv_id: Option<String>, +        jstor_id: Option<String>,          expand: Option<String>,          hide: Option<String>,          _context: &Context, @@ -682,6 +696,8 @@ impl Api for Server {                  &pmid,                  &pmcid,                  &core_id, +                &arxiv_id, +                &jstor_id,                  expand_flags,                  hide_flags,              ) diff --git a/rust/src/entity_crud.rs b/rust/src/entity_crud.rs index ce1c1ed7..a92c45a6 100644 --- a/rust/src/entity_crud.rs +++ b/rust/src/entity_crud.rs @@ -8,7 +8,7 @@  use crate::database_models::*;  use crate::database_schema::*;  use crate::editing::EditContext; -use crate::endpoint_handlers::get_release_files; +use crate::endpoint_handlers::{get_release_files, get_release_filesets, get_release_webcaptures};  use crate::errors::*;  use crate::identifiers::*;  use crate::server::DbConn; @@ -798,8 +798,7 @@ impl EntityCrud for ContainerEntity {              wikidata_qid: None,              publisher: None,              name: None, -            abbrev: None, -            coden: None, +            container_type: None,              state: Some(ident_row.state().unwrap().shortname()),              ident: Some(FatcatId::from_uuid(&ident_row.id).to_string()),              revision: ident_row.rev_id.map(|u| u.to_string()), @@ -831,8 +830,7 @@ impl EntityCrud for ContainerEntity {              wikidata_qid: rev_row.wikidata_qid,              publisher: rev_row.publisher,              name: Some(rev_row.name), -            abbrev: rev_row.abbrev, -            coden: rev_row.coden, +            container_type: rev_row.container_type,              state,              ident: ident_id,              revision: Some(rev_row.id.to_string()), @@ -869,8 +867,7 @@ impl EntityCrud for ContainerEntity {                          publisher: model.publisher.clone(),                          issnl: model.issnl.clone(),                          wikidata_qid: model.wikidata_qid.clone(), -                        abbrev: model.abbrev.clone(), -                        coden: model.coden.clone(), +                        container_type: model.container_type.clone(),                          extra_json: model.extra.clone(),                      })                      .collect::<Vec<ContainerRevNewRow>>(), @@ -1619,6 +1616,7 @@ impl EntityCrud for ReleaseEntity {          Ok(ReleaseEntity {              title: None, +            original_title: None,              release_type: None,              release_status: None,              release_date: None, @@ -1627,8 +1625,10 @@ impl EntityCrud for ReleaseEntity {              pmid: None,              pmcid: None,              isbn13: None, -            core_id: None,              wikidata_qid: None, +            core_id: None, +            arxiv_id: None, +            jstor_id: None,              volume: None,              issue: None,              pages: None, @@ -1639,6 +1639,7 @@ impl EntityCrud for ReleaseEntity {              container_id: None,              publisher: None,              language: None, +            license_slug: None,              work_id: None,              refs: None,              contribs: None, @@ -1675,6 +1676,26 @@ impl EntityCrud for ReleaseEntity {              };              self.files = Some(get_release_files(conn, ident, HideFlags::none())?);          } +        if expand.filesets && self.ident.is_some() { +            let ident = match &self.ident { +                None => bail!("Can't expand filesets on a non-concrete entity"), // redundant with above is_some() +                Some(ident) => match &self.redirect { +                    None => FatcatId::from_str(&ident)?, +                    Some(redir) => FatcatId::from_str(&redir)?, +                }, +            }; +            self.filesets = Some(get_release_filesets(conn, ident, HideFlags::none())?); +        } +        if expand.webcaptures && self.ident.is_some() { +            let ident = match &self.ident { +                None => bail!("Can't expand webcaptures on a non-concrete entity"), // redundant with above is_some() +                Some(ident) => match &self.redirect { +                    None => FatcatId::from_str(&ident)?, +                    Some(redir) => FatcatId::from_str(&redir)?, +                }, +            }; +            self.webcaptures = Some(get_release_webcaptures(conn, ident, HideFlags::none())?); +        }          if expand.container {              if let Some(ref cid) = self.container_id {                  self.container = Some(ContainerEntity::db_get( @@ -1812,28 +1833,28 @@ impl EntityCrud for ReleaseEntity {              None => (None, None, None),          }; -        let refs: Option<Vec<ReleaseRef>> = match hide.refs { -            true => None, -            false => Some( -                release_ref::table +        let refs: Option<Vec<ReleaseRef>> = match (hide.refs, rev_row.refs_blob_sha1) { +            (true, _) => None, +            (false, None) => Some(vec![]), +            (false, Some(sha1)) => Some({ +                let refs_blob: RefsBlobRow = refs_blob::table +                    .find(sha1) // checked in match +                    .get_result(conn)?; +                let refs: Vec<RefsBlobJson> = serde_json::from_value(refs_blob.refs_json)?; +                let mut refs: Vec<ReleaseRef> = refs.into_iter().map(|j| j.into_model()).collect(); +                let ref_rows: Vec<ReleaseRefRow> = release_ref::table                      .filter(release_ref::release_rev.eq(rev_row.id))                      .order(release_ref::index_val.asc()) -                    .get_results(conn)? -                    .into_iter() -                    .map(|r: ReleaseRefRow| ReleaseRef { -                        index: r.index_val.map(|v| v as i64), -                        key: r.key, -                        extra: r.extra_json, -                        container_name: r.container_name, -                        year: r.year.map(|v| v as i64), -                        title: r.title, -                        locator: r.locator, -                        target_release_id: r -                            .target_release_ident_id -                            .map(|v| FatcatId::from_uuid(&v).to_string()), -                    }) -                    .collect(), -            ), +                    .get_results(conn)?; +                for index in 0..refs.len() { +                    refs[index].index = Some(index as i64) +                } +                for row in ref_rows { +                    refs[row.index_val as usize].target_release_id = +                        Some(FatcatId::from_uuid(&row.target_release_ident_id).to_string()); +                } +                refs +            }),          };          let contribs: Option<Vec<ReleaseContrib>> = match hide.contribs { @@ -1851,6 +1872,7 @@ impl EntityCrud for ReleaseEntity {                          index: c.index_val.map(|v| v as i64),                          raw_name: c.raw_name,                          role: c.role, +                        raw_affiliation: c.raw_affiliation,                          extra: c.extra_json,                          creator_id: c                              .creator_ident_id @@ -1884,6 +1906,7 @@ impl EntityCrud for ReleaseEntity {          Ok(ReleaseEntity {              title: Some(rev_row.title), +            original_title: rev_row.original_title,              release_type: rev_row.release_type,              release_status: rev_row.release_status,              release_date: rev_row.release_date, @@ -1892,8 +1915,10 @@ impl EntityCrud for ReleaseEntity {              pmid: rev_row.pmid,              pmcid: rev_row.pmcid,              isbn13: rev_row.isbn13, -            core_id: rev_row.core_id,              wikidata_qid: rev_row.wikidata_qid, +            core_id: rev_row.core_id, +            arxiv_id: rev_row.arxiv_id, +            jstor_id: rev_row.jstor_id,              volume: rev_row.volume,              issue: rev_row.issue,              pages: rev_row.pages, @@ -1906,6 +1931,7 @@ impl EntityCrud for ReleaseEntity {                  .map(|u| FatcatId::from_uuid(&u).to_string()),              publisher: rev_row.publisher,              language: rev_row.language, +            license_slug: rev_row.license_slug,              work_id: Some(FatcatId::from_uuid(&rev_row.work_ident_id).to_string()),              refs,              contribs, @@ -1934,6 +1960,7 @@ impl EntityCrud for ReleaseEntity {              if let Some(ref extid) = entity.wikidata_qid {                  check_wikidata_qid(extid)?;              } +            // TODO: JSTOR and arxiv IDs              if let Some(ref release_type) = entity.release_type {                  check_release_type(release_type)?;              } @@ -1953,13 +1980,65 @@ impl EntityCrud for ReleaseEntity {              .into());          } +        // First, calculate and upsert any refs JSON blobs and record the SHA1 keys, so they can be +        // included in the release_rev row itself +        let mut refs_blob_rows: Vec<RefsBlobRow> = vec![]; +        let mut refs_blob_sha1: Vec<Option<String>> = vec![]; +        for model in models.iter() { +            match &model.refs { +                None => { +                    refs_blob_sha1.push(None); +                } +                Some(ref_list) => { +                    if ref_list.is_empty() { +                        refs_blob_sha1.push(None); +                        continue; +                    } +                    // Have to strip out target refs and indexes, or hashing won't work well when +                    // these change +                    let ref_list: Vec<RefsBlobJson> = ref_list +                        .iter() +                        .map(|r: &ReleaseRef| { +                            let mut r = RefsBlobJson::from_model(r); +                            r.target_release_id = None; +                            r.index = None; +                            r +                        }) +                        .collect(); +                    // TODO: maybe `canonical_json` crate? +                    let refs_json = serde_json::to_value(ref_list)?; +                    let refs_str = refs_json.to_string(); +                    let sha1 = Sha1::from(refs_str).hexdigest(); +                    let blob = RefsBlobRow { +                        sha1: sha1.clone(), +                        refs_json, +                    }; +                    refs_blob_rows.push(blob); +                    refs_blob_sha1.push(Some(sha1)); +                } +            }; +        } + +        if !refs_blob_rows.is_empty() { +            // Sort of an "upsert"; only inserts new abstract rows if they don't already exist +            insert_into(refs_blob::table) +                .values(&refs_blob_rows) +                .on_conflict(refs_blob::sha1) +                .do_nothing() +                .execute(conn)?; +        } + +        // Then the main release_revs themselves          let rev_ids: Vec<Uuid> = insert_into(release_rev::table)              .values(                  models                      .iter() -                    .map(|model| { +                    .zip(refs_blob_sha1.into_iter()) +                    .map(|(model, refs_sha1)| {                          Ok(ReleaseRevNewRow { +                    refs_blob_sha1: refs_sha1,                      title: model.title.clone().unwrap(), // titles checked above +                    original_title: model.original_title.clone(),                      release_type: model.release_type.clone(),                      release_status: model.release_status.clone(),                      release_date: model.release_date, @@ -1970,6 +2049,8 @@ impl EntityCrud for ReleaseEntity {                      wikidata_qid: model.wikidata_qid.clone(),                      isbn13: model.isbn13.clone(),                      core_id: model.core_id.clone(), +                    arxiv_id: model.arxiv_id.clone(), +                    jstor_id: model.jstor_id.clone(),                      volume: model.volume.clone(),                      issue: model.issue.clone(),                      pages: model.pages.clone(), @@ -1983,6 +2064,7 @@ impl EntityCrud for ReleaseEntity {                      },                      publisher: model.publisher.clone(),                      language: model.language.clone(), +                    license_slug: model.license_slug.clone(),                      extra_json: model.extra.clone()                  })                      }) @@ -1991,34 +2073,32 @@ impl EntityCrud for ReleaseEntity {              .returning(release_rev::id)              .get_results(conn)?; -        let mut release_ref_rows: Vec<ReleaseRefNewRow> = vec![]; +        let mut release_ref_rows: Vec<ReleaseRefRow> = vec![];          let mut release_contrib_rows: Vec<ReleaseContribNewRow> = vec![];          let mut abstract_rows: Vec<AbstractsRow> = vec![];          let mut release_abstract_rows: Vec<ReleaseRevAbstractNewRow> = vec![];          for (model, rev_id) in models.iter().zip(rev_ids.iter()) { +            // We didn't know the release_rev id to insert here, so need to re-iterate over refs              match &model.refs {                  None => (),                  Some(ref_list) => { -                    let these_ref_rows: Vec<ReleaseRefNewRow> = ref_list +                    let these_ref_rows: Vec<ReleaseRefRow> = ref_list                          .iter() -                        .map(|r| { -                            Ok(ReleaseRefNewRow { +                        .enumerate() +                        .filter(|(_, r)| r.target_release_id.is_some()) +                        .map(|(index, r)| { +                            Ok(ReleaseRefRow {                                  release_rev: *rev_id, -                                target_release_ident_id: match r.target_release_id.clone() { -                                    None => None, -                                    Some(v) => Some(FatcatId::from_str(&v)?.to_uuid()), -                                }, -                                index_val: r.index.map(|v| v as i32), -                                key: r.key.clone(), -                                container_name: r.container_name.clone(), -                                year: r.year.map(|v| v as i32), -                                title: r.title.clone(), -                                locator: r.locator.clone(), -                                extra_json: r.extra.clone(), +                                // unwrap() checked by is_some() filter +                                target_release_ident_id: FatcatId::from_str( +                                    &r.target_release_id.clone().unwrap(), +                                )? +                                .to_uuid(), +                                index_val: index as i32,                              })                          }) -                        .collect::<Result<Vec<ReleaseRefNewRow>>>()?; +                        .collect::<Result<Vec<ReleaseRefRow>>>()?;                      release_ref_rows.extend(these_ref_rows);                  }              }; @@ -2038,6 +2118,7 @@ impl EntityCrud for ReleaseEntity {                                  raw_name: c.raw_name.clone(),                                  index_val: c.index.map(|v| v as i32),                                  role: c.role.clone(), +                                raw_affiliation: c.raw_affiliation.clone(),                                  extra_json: c.extra.clone(),                              })                          }) @@ -2053,7 +2134,7 @@ impl EntityCrud for ReleaseEntity {                      .iter()                      .filter(|ea| ea.content.is_some())                      .map(|c| AbstractsRow { -                        sha1: Sha1::from(c.content.clone().unwrap()).hexdigest(), +                        sha1: Sha1::from(c.content.as_ref().unwrap()).hexdigest(),                          content: c.content.clone().unwrap(),                      })                      .collect(); diff --git a/rust/src/lib.rs b/rust/src/lib.rs index b7661334..d089adf8 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -9,6 +9,8 @@ extern crate log;  extern crate lazy_static;  #[macro_use]  extern crate failure; +#[macro_use] +extern crate serde_derive;  pub mod auth;  pub mod database_models; | 
