aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBryan Newbold <bnewbold@robocracy.org>2018-09-07 18:57:11 -0700
committerBryan Newbold <bnewbold@robocracy.org>2018-09-07 18:57:15 -0700
commit47cb21bdc31466dd827800898a4ad543a6297696 (patch)
tree9150816a6403972a78b3853cc264330eba4cff7a
parente73b56f9354596c556cfbb2d45584a6bb86ad60e (diff)
downloadfatcat-47cb21bdc31466dd827800898a4ad543a6297696.tar.gz
fatcat-47cb21bdc31466dd827800898a4ad543a6297696.zip
mostly done with CRUD refactor
One failing test in this commit.
-rw-r--r--rust/src/api_server.rs687
-rw-r--r--rust/src/database_entity_crud.rs626
-rw-r--r--rust/src/database_models.rs57
3 files changed, 745 insertions, 625 deletions
diff --git a/rust/src/api_server.rs b/rust/src/api_server.rs
index 5060ba0f..ecf0c242 100644
--- a/rust/src/api_server.rs
+++ b/rust/src/api_server.rs
@@ -21,47 +21,16 @@ use database_entity_crud::{EntityCrud, EditContext};
use std::str::FromStr;
macro_rules! entity_batch_handler {
- ($post_handler:ident, $post_batch_handler:ident, $model:ident) => {
+ ($post_batch_handler:ident, $model:ident) => {
pub fn $post_batch_handler(
&self,
entity_list: &[models::$model],
conn: &DbConn,
) -> Result<Vec<EntityEdit>> {
- let mut ret: Vec<EntityEdit> = vec![];
- for entity in entity_list {
- ret.push(self.$post_handler(entity.clone(), conn)?);
- }
- Ok(ret)
- }
- }
-}
-
-macro_rules! entity_history_handler {
- ($history_handler:ident, $edit_row_type:ident, $edit_table:ident) => {
- pub fn $history_handler(
- &self,
- id: &Uuid,
- limit: Option<i64>,
- conn: &DbConn,
- ) -> Result<Vec<EntityHistoryEntry>> {
- let limit = limit.unwrap_or(50);
-
- let rows: Vec<(EditgroupRow, ChangelogRow, $edit_row_type)> = editgroup::table
- .inner_join(changelog::table)
- .inner_join($edit_table::table)
- .filter($edit_table::ident_id.eq(id))
- .order(changelog::id.desc())
- .limit(limit)
- .get_results(conn)?;
-
- let history: Vec<EntityHistoryEntry> = rows.into_iter()
- .map(|(eg_row, cl_row, e_row)| EntityHistoryEntry {
- edit: e_row.into_model().expect("edit row to model"),
- editgroup: eg_row.into_model_partial(),
- changelog_entry: cl_row.into_model(),
- })
- .collect();
- Ok(history)
+ let edit_context = make_edit_context(conn, None)?;
+ 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())?;
+ edits.into_iter().map(|e| e.into_model()).collect()
}
}
}
@@ -96,226 +65,6 @@ pub struct Server {
pub db_pool: ConnectionPool,
}
-fn container_row2entity(
- ident: Option<ContainerIdentRow>,
- rev: ContainerRevRow,
-) -> Result<ContainerEntity> {
- let (state, ident_id, redirect_id) = match ident {
- Some(i) => (
- Some(i.state().unwrap().shortname()),
- Some(uuid2fcid(&i.id)),
- i.redirect_id.map(|u| uuid2fcid(&u)),
- ),
- None => (None, None, None),
- };
- Ok(ContainerEntity {
- issnl: rev.issnl,
- wikidata_qid: rev.wikidata_qid,
- publisher: rev.publisher,
- name: rev.name,
- abbrev: rev.abbrev,
- coden: rev.coden,
- state: state,
- ident: ident_id,
- revision: Some(rev.id.to_string()),
- redirect: redirect_id,
- extra: rev.extra_json,
- editgroup_id: None,
- })
-}
-
-fn creator_row2entity(ident: Option<CreatorIdentRow>, rev: CreatorRevRow) -> Result<CreatorEntity> {
- let (state, ident_id, redirect_id) = match ident {
- Some(i) => (
- Some(i.state().unwrap().shortname()),
- Some(uuid2fcid(&i.id)),
- i.redirect_id.map(|u| uuid2fcid(&u)),
- ),
- None => (None, None, None),
- };
- Ok(CreatorEntity {
- display_name: rev.display_name,
- given_name: rev.given_name,
- surname: rev.surname,
- orcid: rev.orcid,
- wikidata_qid: rev.wikidata_qid,
- state: state,
- ident: ident_id,
- revision: Some(rev.id.to_string()),
- redirect: redirect_id,
- editgroup_id: None,
- extra: rev.extra_json,
- })
-}
-
-fn file_row2entity(
- ident: Option<FileIdentRow>,
- rev: FileRevRow,
- conn: &DbConn,
-) -> Result<FileEntity> {
- let (state, ident_id, redirect_id) = match ident {
- Some(i) => (
- Some(i.state().unwrap().shortname()),
- Some(uuid2fcid(&i.id)),
- i.redirect_id.map(|u| uuid2fcid(&u)),
- ),
- None => (None, None, None),
- };
-
- let releases: Vec<String> = file_release::table
- .filter(file_release::file_rev.eq(rev.id))
- .get_results(conn)?
- .into_iter()
- .map(|r: FileReleaseRow| uuid2fcid(&r.target_release_ident_id))
- .collect();
-
- let urls: Vec<FileEntityUrls> = file_rev_url::table
- .filter(file_rev_url::file_rev.eq(rev.id))
- .get_results(conn)?
- .into_iter()
- .map(|r: FileRevUrlRow| FileEntityUrls {
- rel: r.rel,
- url: r.url,
- })
- .collect();
-
- Ok(FileEntity {
- sha1: rev.sha1,
- sha256: rev.sha256,
- md5: rev.md5,
- size: rev.size.map(|v| v as i64),
- urls: Some(urls),
- mimetype: rev.mimetype,
- releases: Some(releases),
- state: state,
- ident: ident_id,
- revision: Some(rev.id.to_string()),
- redirect: redirect_id,
- editgroup_id: None,
- extra: rev.extra_json,
- })
-}
-
-fn release_row2entity(
- ident: Option<ReleaseIdentRow>,
- rev: ReleaseRevRow,
- conn: &DbConn,
-) -> Result<ReleaseEntity> {
- let (state, ident_id, redirect_id) = match ident {
- Some(i) => (
- Some(i.state().unwrap().shortname()),
- Some(uuid2fcid(&i.id)),
- i.redirect_id.map(|u| uuid2fcid(&u)),
- ),
- None => (None, None, None),
- };
-
- let refs: Vec<ReleaseRef> = release_ref::table
- .filter(release_ref::release_rev.eq(rev.id))
- .order(release_ref::index_val.asc())
- .get_results(conn)
- .expect("fetch release refs")
- .into_iter()
- .map(|r: ReleaseRefRow| ReleaseRef {
- index: r.index_val,
- key: r.key,
- extra: r.extra_json,
- container_title: r.container_title,
- year: r.year,
- title: r.title,
- locator: r.locator,
- target_release_id: r.target_release_ident_id.map(|v| uuid2fcid(&v)),
- })
- .collect();
-
- let contribs: Vec<ReleaseContrib> = release_contrib::table
- .filter(release_contrib::release_rev.eq(rev.id))
- .order((
- release_contrib::role.asc(),
- release_contrib::index_val.asc(),
- ))
- .get_results(conn)
- .expect("fetch release refs")
- .into_iter()
- .map(|c: ReleaseContribRow| ReleaseContrib {
- index: c.index_val,
- raw_name: c.raw_name,
- role: c.role,
- extra: c.extra_json,
- creator_id: c.creator_ident_id.map(|v| uuid2fcid(&v)),
- creator: None,
- })
- .collect();
-
- let abstracts: Vec<ReleaseEntityAbstracts> = release_rev_abstract::table
- .inner_join(abstracts::table)
- .filter(release_rev_abstract::release_rev.eq(rev.id))
- .get_results(conn)?
- .into_iter()
- .map(
- |r: (ReleaseRevAbstractRow, AbstractsRow)| ReleaseEntityAbstracts {
- sha1: Some(r.0.abstract_sha1),
- mimetype: r.0.mimetype,
- lang: r.0.lang,
- content: Some(r.1.content),
- },
- )
- .collect();
-
- Ok(ReleaseEntity {
- title: rev.title,
- release_type: rev.release_type,
- release_status: rev.release_status,
- release_date: rev.release_date
- .map(|v| chrono::DateTime::from_utc(v.and_hms(0, 0, 0), chrono::Utc)),
- doi: rev.doi,
- pmid: rev.pmid,
- pmcid: rev.pmcid,
- isbn13: rev.isbn13,
- core_id: rev.core_id,
- wikidata_qid: rev.wikidata_qid,
- volume: rev.volume,
- issue: rev.issue,
- pages: rev.pages,
- files: None,
- container: None,
- container_id: rev.container_ident_id.map(|u| uuid2fcid(&u)),
- publisher: rev.publisher,
- language: rev.language,
- work_id: Some(uuid2fcid(&rev.work_ident_id)),
- refs: Some(refs),
- contribs: Some(contribs),
- abstracts: Some(abstracts),
- state: state,
- ident: ident_id,
- revision: Some(rev.id.to_string()),
- redirect: redirect_id,
- editgroup_id: None,
- extra: rev.extra_json,
- })
-}
-
-/* XXX:
-fn work_row2entity(ident: Option<WorkIdentRow>, rev: WorkRevRow) -> Result<WorkEntity> {
- let (state, ident_id, redirect_id) = match ident {
- Some(i) => (
- Some(i.state().unwrap().shortname()),
- Some(uuid2fcid(&i.id)),
- i.redirect_id.map(|u| uuid2fcid(&u)),
- ),
- None => (None, None, None),
- };
- Ok(WorkEntity {
- state: state,
- ident: ident_id,
- revision: Some(rev.id.to_string()),
- redirect: redirect_id,
- editgroup_id: None,
- extra: rev.extra_json,
- })
-}
-*/
-
impl Server {
pub fn get_container_handler(
&self,
@@ -323,13 +72,7 @@ impl Server {
_expand: Option<String>,
conn: &DbConn,
) -> Result<ContainerEntity> {
- // TODO: handle Deletions
- let (ident, rev): (ContainerIdentRow, ContainerRevRow) = container_ident::table
- .find(id)
- .inner_join(container_rev::table)
- .first(conn)?;
-
- container_row2entity(Some(ident), rev)
+ ContainerEntity::db_get(conn, FatCatId::from_uuid(id))
}
pub fn lookup_container_handler(&self, issnl: &str, conn: &DbConn) -> Result<ContainerEntity> {
@@ -344,7 +87,7 @@ impl Server {
.filter(container_ident::redirect_id.is_null())
.first(conn)?;
- container_row2entity(Some(ident), rev)
+ ContainerEntity::db_from_row(conn, rev, Some(ident))
}
pub fn get_creator_handler(
@@ -353,12 +96,8 @@ impl Server {
_expand: Option<String>,
conn: &DbConn,
) -> Result<CreatorEntity> {
- let (ident, rev): (CreatorIdentRow, CreatorRevRow) = creator_ident::table
- .find(id)
- .inner_join(creator_rev::table)
- .first(conn)?;
- creator_row2entity(Some(ident), rev)
+ CreatorEntity::db_get(conn, FatCatId::from_uuid(id))
}
pub fn lookup_creator_handler(&self, orcid: &str, conn: &DbConn) -> Result<CreatorEntity> {
@@ -373,7 +112,7 @@ impl Server {
.filter(creator_ident::redirect_id.is_null())
.first(conn)?;
- creator_row2entity(Some(ident), rev)
+ CreatorEntity::db_from_row(conn, rev, Some(ident))
}
pub fn get_creator_releases_handler(
@@ -392,8 +131,9 @@ impl Server {
.filter(release_ident::redirect_id.is_null())
.load(conn)?;
+ // TODO: from_rows, not from_row?
rows.into_iter()
- .map(|(rev, ident, _)| release_row2entity(Some(ident), rev, conn))
+ .map(|(rev, ident, _)| ReleaseEntity::db_from_row(conn, rev, Some(ident)))
.collect()
}
@@ -403,12 +143,7 @@ impl Server {
_expand: Option<String>,
conn: &DbConn,
) -> Result<FileEntity> {
- let (ident, rev): (FileIdentRow, FileRevRow) = file_ident::table
- .find(id)
- .inner_join(file_rev::table)
- .first(conn)?;
-
- file_row2entity(Some(ident), rev, conn)
+ FileEntity::db_get(conn, FatCatId::from_uuid(id))
}
pub fn lookup_file_handler(&self, sha1: &str, conn: &DbConn) -> Result<FileEntity> {
@@ -422,7 +157,7 @@ impl Server {
.filter(file_ident::redirect_id.is_null())
.first(conn)?;
- file_row2entity(Some(ident), rev, conn)
+ FileEntity::db_from_row(conn, rev, Some(ident))
}
pub fn get_release_handler(
@@ -431,12 +166,8 @@ impl Server {
expand: Option<String>,
conn: &DbConn,
) -> Result<ReleaseEntity> {
- let (ident, rev): (ReleaseIdentRow, ReleaseRevRow) = release_ident::table
- .find(id)
- .inner_join(release_rev::table)
- .first(conn)?;
- let mut release = release_row2entity(Some(ident), rev, conn)?;
+ let mut release = ReleaseEntity::db_get(conn, FatCatId::from_uuid(id))?;
// For now, if there is any expand param we do them all
if expand.is_some() {
@@ -447,7 +178,6 @@ impl Server {
Some(self.get_container_handler(&fcid2uuid(&cid)?, None, conn)?);
}
}
-
Ok(release)
}
@@ -463,22 +193,23 @@ impl Server {
.filter(release_ident::redirect_id.is_null())
.first(conn)?;
- release_row2entity(Some(ident), rev, conn)
+ ReleaseEntity::db_from_row(conn, rev, Some(ident))
}
pub fn get_release_files_handler(&self, id: &str, conn: &DbConn) -> Result<Vec<FileEntity>> {
- let id = fcid2uuid(&id)?;
+
+ let ident = FatCatId::from_str(id)?;
let rows: Vec<(FileRevRow, FileIdentRow, FileReleaseRow)> = file_rev::table
.inner_join(file_ident::table)
.inner_join(file_release::table)
- .filter(file_release::target_release_ident_id.eq(&id))
+ .filter(file_release::target_release_ident_id.eq(&ident.to_uuid()))
.filter(file_ident::is_live.eq(true))
.filter(file_ident::redirect_id.is_null())
.load(conn)?;
rows.into_iter()
- .map(|(rev, ident, _)| file_row2entity(Some(ident), rev, conn))
+ .map(|(rev, ident, _)| FileEntity::db_from_row(conn, rev, Some(ident)))
.collect()
}
@@ -502,7 +233,7 @@ impl Server {
.load(conn)?;
rows.into_iter()
- .map(|(rev, ident)| release_row2entity(Some(ident), rev, conn))
+ .map(|(rev, ident)| ReleaseEntity::db_from_row(conn, rev, Some(ident)))
.collect()
}
@@ -511,52 +242,25 @@ impl Server {
entity: models::ContainerEntity,
conn: &DbConn,
) -> Result<EntityEdit> {
- let editor_id = Uuid::parse_str("00000000-0000-0000-AAAA-000000000001")?; // TODO: auth
- let editgroup_id: Uuid = match entity.editgroup_id {
- None => get_or_create_editgroup(editor_id, conn)?,
- Some(param) => fcid2uuid(&param)?,
- };
- if let Some(ref extid) = entity.wikidata_qid {
- check_wikidata_qid(extid)?;
- }
- if let Some(ref extid) = entity.issnl {
- check_issn(extid)?;
- }
-
- let edit: ContainerEditRow = diesel::sql_query(
- "WITH rev AS ( INSERT INTO container_rev (name, publisher, issnl, wikidata_qid, abbrev, coden, extra_json)
- VALUES ($1, $2, $3, $4, $5, $6, $7)
- RETURNING id ),
- ident AS ( INSERT INTO container_ident (rev_id)
- VALUES ((SELECT rev.id FROM rev))
- RETURNING id )
- INSERT INTO container_edit (editgroup_id, ident_id, rev_id) VALUES
- ($8, (SELECT ident.id FROM ident), (SELECT rev.id FROM rev))
- RETURNING *",
- ).bind::<diesel::sql_types::Text, _>(entity.name)
- .bind::<diesel::sql_types::Nullable<diesel::sql_types::Text>, _>(entity.publisher)
- .bind::<diesel::sql_types::Nullable<diesel::sql_types::Text>, _>(entity.issnl)
- .bind::<diesel::sql_types::Nullable<diesel::sql_types::Text>, _>(entity.wikidata_qid)
- .bind::<diesel::sql_types::Nullable<diesel::sql_types::Text>, _>(entity.abbrev)
- .bind::<diesel::sql_types::Nullable<diesel::sql_types::Text>, _>(entity.coden)
- .bind::<diesel::sql_types::Nullable<diesel::sql_types::Json>, _>(entity.extra)
- .bind::<diesel::sql_types::Uuid, _>(editgroup_id)
- .get_result(conn)?;
-
+ let edit_context = make_edit_context(conn, entity.parse_editgroup_id()?)?;
+ let edit = entity.db_create(conn, &edit_context)?;
edit.into_model()
}
- // XXX:
pub fn update_container_handler(
&self,
id: &Uuid,
entity: models::ContainerEntity,
conn: &DbConn,
) -> Result<EntityEdit> {
- unimplemented!()
+ let edit_context = make_edit_context(conn, entity.parse_editgroup_id()?)?;
+ let edit = entity.db_update(conn, &edit_context, FatCatId::from_uuid(id))?;
+ edit.into_model()
}
pub fn delete_container_handler(&self, id: &Uuid, editgroup_id: Option<Uuid>, conn: &DbConn) -> Result<EntityEdit> {
- unimplemented!()
+ let edit_context = make_edit_context(conn, editgroup_id.map(|u| FatCatId::from_uuid(&u)))?;
+ let edit = ContainerEntity::db_delete(conn, &edit_context, FatCatId::from_uuid(id))?;
+ edit.into_model()
}
pub fn create_creator_handler(
@@ -564,51 +268,26 @@ impl Server {
entity: models::CreatorEntity,
conn: &DbConn,
) -> Result<EntityEdit> {
- let editor_id = Uuid::parse_str("00000000-0000-0000-AAAA-000000000001")?; // TODO: auth
- let editgroup_id = match entity.editgroup_id {
- None => get_or_create_editgroup(editor_id, conn).expect("current editgroup"),
- Some(param) => fcid2uuid(&param)?,
- };
- if let Some(ref extid) = entity.orcid {
- check_orcid(extid)?;
- }
- if let Some(ref extid) = entity.wikidata_qid {
- check_wikidata_qid(extid)?;
- }
-
- let edit: CreatorEditRow = diesel::sql_query(
- "WITH rev AS ( INSERT INTO creator_rev (display_name, given_name, surname, orcid, wikidata_qid, extra_json)
- VALUES ($1, $2, $3, $4, $5, $6)
- RETURNING id ),
- ident AS ( INSERT INTO creator_ident (rev_id)
- VALUES ((SELECT rev.id FROM rev))
- RETURNING id )
- INSERT INTO creator_edit (editgroup_id, ident_id, rev_id) VALUES
- ($7, (SELECT ident.id FROM ident), (SELECT rev.id FROM rev))
- RETURNING *",
- ).bind::<diesel::sql_types::Text, _>(entity.display_name)
- .bind::<diesel::sql_types::Nullable<diesel::sql_types::Text>, _>(entity.given_name)
- .bind::<diesel::sql_types::Nullable<diesel::sql_types::Text>, _>(entity.surname)
- .bind::<diesel::sql_types::Nullable<diesel::sql_types::Text>, _>(entity.orcid)
- .bind::<diesel::sql_types::Nullable<diesel::sql_types::Text>, _>(entity.wikidata_qid)
- .bind::<diesel::sql_types::Nullable<diesel::sql_types::Json>, _>(entity.extra)
- .bind::<diesel::sql_types::Uuid, _>(editgroup_id)
- .get_result(conn)?;
-
+ let edit_context = make_edit_context(conn, entity.parse_editgroup_id()?)?;
+ let edit = entity.db_create(conn, &edit_context)?;
edit.into_model()
+
}
- // XXX:
pub fn update_creator_handler(
&self,
id: &Uuid,
entity: models::CreatorEntity,
conn: &DbConn,
) -> Result<EntityEdit> {
- unimplemented!()
+ let edit_context = make_edit_context(conn, entity.parse_editgroup_id()?)?;
+ let edit = entity.db_update(conn, &edit_context, FatCatId::from_uuid(id))?;
+ edit.into_model()
}
pub fn delete_creator_handler(&self, id: &Uuid, editgroup_id: Option<Uuid>, conn: &DbConn) -> Result<EntityEdit> {
- unimplemented!()
+ let edit_context = make_edit_context(conn, editgroup_id.map(|u| FatCatId::from_uuid(&u)))?;
+ let edit = CreatorEntity::db_delete(conn, &edit_context, FatCatId::from_uuid(id))?;
+ edit.into_model()
}
pub fn create_file_handler(
@@ -616,92 +295,25 @@ impl Server {
entity: models::FileEntity,
conn: &DbConn,
) -> Result<EntityEdit> {
- let editor_id = Uuid::parse_str("00000000-0000-0000-AAAA-000000000001")?; // TODO: auth
- let editgroup_id = match entity.editgroup_id {
- None => get_or_create_editgroup(editor_id, conn).expect("current editgroup"),
- Some(param) => fcid2uuid(&param)?,
- };
-
- let edit: FileEditRow =
- diesel::sql_query(
- "WITH rev AS ( INSERT INTO file_rev (size, sha1, sha256, md5, mimetype, extra_json)
- VALUES ($1, $2, $3, $4, $5, $6)
- RETURNING id ),
- ident AS ( INSERT INTO file_ident (rev_id)
- VALUES ((SELECT rev.id FROM rev))
- RETURNING id )
- INSERT INTO file_edit (editgroup_id, ident_id, rev_id) VALUES
- ($7, (SELECT ident.id FROM ident), (SELECT rev.id FROM rev))
- RETURNING *",
- ).bind::<diesel::sql_types::Nullable<diesel::sql_types::Int8>, _>(entity.size)
- .bind::<diesel::sql_types::Nullable<diesel::sql_types::Text>, _>(entity.sha1)
- .bind::<diesel::sql_types::Nullable<diesel::sql_types::Text>, _>(entity.sha256)
- .bind::<diesel::sql_types::Nullable<diesel::sql_types::Text>, _>(entity.md5)
- .bind::<diesel::sql_types::Nullable<diesel::sql_types::Text>, _>(entity.mimetype)
- .bind::<diesel::sql_types::Nullable<diesel::sql_types::Json>, _>(entity.extra)
- .bind::<diesel::sql_types::Uuid, _>(editgroup_id)
- .get_result(conn)?;
-
- let _releases: Option<Vec<FileReleaseRow>> = match entity.releases {
- None => None,
- Some(release_list) => {
- if release_list.is_empty() {
- Some(vec![])
- } else {
- let release_rows: Vec<FileReleaseRow> = release_list
- .iter()
- .map(|r| FileReleaseRow {
- file_rev: edit.rev_id.unwrap(),
- target_release_ident_id: fcid2uuid(r)
- .expect("invalid fatcat identifier"),
- })
- .collect();
- let release_rows: Vec<FileReleaseRow> = insert_into(file_release::table)
- .values(release_rows)
- .get_results(conn)
- .expect("error inserting file_releases");
- Some(release_rows)
- }
- }
- };
-
- let _urls: Option<Vec<FileRevUrlRow>> = match entity.urls {
- None => None,
- Some(url_list) => {
- if url_list.is_empty() {
- Some(vec![])
- } else {
- let url_rows: Vec<FileRevUrlNewRow> = url_list
- .into_iter()
- .map(|u| FileRevUrlNewRow {
- file_rev: edit.rev_id.unwrap(),
- rel: u.rel,
- url: u.url,
- })
- .collect();
- let url_rows: Vec<FileRevUrlRow> = insert_into(file_rev_url::table)
- .values(url_rows)
- .get_results(conn)
- .expect("error inserting file_rev_url");
- Some(url_rows)
- }
- }
- };
-
+ let edit_context = make_edit_context(conn, entity.parse_editgroup_id()?)?;
+ let edit = entity.db_create(conn, &edit_context)?;
edit.into_model()
}
- // XXX:
pub fn update_file_handler(
&self,
id: &Uuid,
entity: models::FileEntity,
conn: &DbConn,
) -> Result<EntityEdit> {
- unimplemented!()
+ let edit_context = make_edit_context(conn, entity.parse_editgroup_id()?)?;
+ let edit = entity.db_update(conn, &edit_context, FatCatId::from_uuid(id))?;
+ edit.into_model()
}
pub fn delete_file_handler(&self, id: &Uuid, editgroup_id: Option<Uuid>, conn: &DbConn) -> Result<EntityEdit> {
- unimplemented!()
+ let edit_context = make_edit_context(conn, editgroup_id.map(|u| FatCatId::from_uuid(&u)))?;
+ let edit = FileEntity::db_delete(conn, &edit_context, FatCatId::from_uuid(id))?;
+ edit.into_model()
}
pub fn create_release_handler(
@@ -709,187 +321,25 @@ impl Server {
entity: models::ReleaseEntity,
conn: &DbConn,
) -> Result<EntityEdit> {
- let editor_id = Uuid::parse_str("00000000-0000-0000-AAAA-000000000001")?; // TODO: auth
- let editgroup_id = match entity.editgroup_id {
- None => get_or_create_editgroup(editor_id, conn).expect("current editgroup"),
- Some(param) => fcid2uuid(&param)?,
- };
- if let Some(ref extid) = entity.doi {
- check_doi(extid)?;
- }
- if let Some(ref extid) = entity.pmid {
- check_pmid(extid)?;
- }
- if let Some(ref extid) = entity.pmcid {
- check_pmcid(extid)?;
- }
- if let Some(ref extid) = entity.wikidata_qid {
- check_wikidata_qid(extid)?;
- }
-
- let work_id = match entity.work_id {
- Some(work_id) => fcid2uuid(&work_id)?,
- None => {
- // If a work_id wasn't passed, create a new work under the current editgroup
- let work_model = models::WorkEntity {
- ident: None,
- revision: None,
- redirect: None,
- state: None,
- editgroup_id: Some(uuid2fcid(&editgroup_id)),
- extra: None,
- };
- let new_entity = self.create_work_handler(work_model, conn)?;
- fcid2uuid(&new_entity.ident)?
- }
- };
-
- let container_id: Option<Uuid> = match entity.container_id {
- Some(id) => Some(fcid2uuid(&id)?),
- None => None,
- };
-
- let edit: ReleaseEditRow = diesel::sql_query(
- "WITH rev AS ( INSERT INTO release_rev (title, release_type, release_status, release_date, doi, pmid, pmcid, wikidata_qid, isbn13, core_id, volume, issue, pages, work_ident_id, container_ident_id, publisher, language, extra_json)
- VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18)
- RETURNING id ),
- ident AS ( INSERT INTO release_ident (rev_id)
- VALUES ((SELECT rev.id FROM rev))
- RETURNING id )
- INSERT INTO release_edit (editgroup_id, ident_id, rev_id) VALUES
- ($19, (SELECT ident.id FROM ident), (SELECT rev.id FROM rev))
- RETURNING *",
- ).bind::<diesel::sql_types::Text, _>(entity.title)
- .bind::<diesel::sql_types::Nullable<diesel::sql_types::Text>, _>(entity.release_type)
- .bind::<diesel::sql_types::Nullable<diesel::sql_types::Text>, _>(entity.release_status)
- .bind::<diesel::sql_types::Nullable<diesel::sql_types::Date>, _>(
- entity.release_date.map(|v| v.naive_utc().date()))
- .bind::<diesel::sql_types::Nullable<diesel::sql_types::Text>, _>(entity.doi)
- .bind::<diesel::sql_types::Nullable<diesel::sql_types::Text>, _>(entity.pmid)
- .bind::<diesel::sql_types::Nullable<diesel::sql_types::Text>, _>(entity.pmcid)
- .bind::<diesel::sql_types::Nullable<diesel::sql_types::Text>, _>(entity.wikidata_qid)
- .bind::<diesel::sql_types::Nullable<diesel::sql_types::Text>, _>(entity.isbn13)
- .bind::<diesel::sql_types::Nullable<diesel::sql_types::Text>, _>(entity.core_id)
- .bind::<diesel::sql_types::Nullable<diesel::sql_types::Text>, _>(entity.volume)
- .bind::<diesel::sql_types::Nullable<diesel::sql_types::Text>, _>(entity.issue)
- .bind::<diesel::sql_types::Nullable<diesel::sql_types::Text>, _>(entity.pages)
- .bind::<diesel::sql_types::Uuid, _>(work_id)
- .bind::<diesel::sql_types::Nullable<diesel::sql_types::Uuid>, _>(container_id)
- .bind::<diesel::sql_types::Nullable<diesel::sql_types::Text>, _>(entity.publisher)
- .bind::<diesel::sql_types::Nullable<diesel::sql_types::Text>, _>(entity.language)
- .bind::<diesel::sql_types::Nullable<diesel::sql_types::Json>, _>(entity.extra)
- .bind::<diesel::sql_types::Uuid, _>(editgroup_id)
- .get_result(conn)?;
-
- let _refs: Option<Vec<ReleaseRefRow>> = match entity.refs {
- None => None,
- Some(ref_list) => {
- if ref_list.is_empty() {
- Some(vec![])
- } else {
- let ref_rows: Vec<ReleaseRefNewRow> = ref_list
- .iter()
- .map(|r| ReleaseRefNewRow {
- release_rev: edit.rev_id.unwrap(),
- target_release_ident_id: r.target_release_id
- .clone()
- .map(|v| fcid2uuid(&v).expect("valid fatcat identifier")),
- index_val: r.index,
- key: r.key.clone(),
- container_title: r.container_title.clone(),
- year: r.year,
- title: r.title.clone(),
- locator: r.locator.clone(),
- extra_json: r.extra.clone(),
- })
- .collect();
- let ref_rows: Vec<ReleaseRefRow> = insert_into(release_ref::table)
- .values(ref_rows)
- .get_results(conn)
- .expect("error inserting release_refs");
- Some(ref_rows)
- }
- }
- };
-
- let _contribs: Option<Vec<ReleaseContribRow>> = match entity.contribs {
- None => None,
- Some(contrib_list) => {
- if contrib_list.is_empty() {
- Some(vec![])
- } else {
- let contrib_rows: Vec<ReleaseContribNewRow> = contrib_list
- .iter()
- .map(|c| ReleaseContribNewRow {
- release_rev: edit.rev_id.unwrap(),
- creator_ident_id: c.creator_id
- .clone()
- .map(|v| fcid2uuid(&v).expect("valid fatcat identifier")),
- raw_name: c.raw_name.clone(),
- index_val: c.index,
- role: c.role.clone(),
- extra_json: c.extra.clone(),
- })
- .collect();
- let contrib_rows: Vec<ReleaseContribRow> = insert_into(release_contrib::table)
- .values(contrib_rows)
- .get_results(conn)
- .expect("error inserting release_contribs");
- Some(contrib_rows)
- }
- }
- };
-
- if let Some(abstract_list) = entity.abstracts {
- // For rows that specify content, we need to insert the abstract if it doesn't exist
- // already
- let new_abstracts: Vec<AbstractsRow> = abstract_list
- .iter()
- .filter(|ea| ea.content.is_some())
- .map(|c| AbstractsRow {
- sha1: Sha1::from(c.content.clone().unwrap()).hexdigest(),
- content: c.content.clone().unwrap(),
- })
- .collect();
- if !new_abstracts.is_empty() {
- // Sort of an "upsert"; only inserts new abstract rows if they don't already exist
- insert_into(abstracts::table)
- .values(&new_abstracts)
- .on_conflict(abstracts::sha1)
- .do_nothing()
- .execute(conn)?;
- }
- let release_abstract_rows: Vec<ReleaseRevAbstractNewRow> = abstract_list
- .into_iter()
- .map(|c| ReleaseRevAbstractNewRow {
- release_rev: edit.rev_id.unwrap(),
- abstract_sha1: match c.content {
- Some(ref content) => Sha1::from(content).hexdigest(),
- None => c.sha1.expect("either abstract_sha1 or content is required"),
- },
- lang: c.lang,
- mimetype: c.mimetype,
- })
- .collect();
- insert_into(release_rev_abstract::table)
- .values(release_abstract_rows)
- .execute(conn)?;
- }
-
+ let edit_context = make_edit_context(conn, entity.parse_editgroup_id()?)?;
+ let edit = entity.db_create(conn, &edit_context)?;
edit.into_model()
}
- // XXX:
pub fn update_release_handler(
&self,
id: &Uuid,
entity: models::ReleaseEntity,
conn: &DbConn,
) -> Result<EntityEdit> {
- unimplemented!()
+ let edit_context = make_edit_context(conn, entity.parse_editgroup_id()?)?;
+ let edit = entity.db_update(conn, &edit_context, FatCatId::from_uuid(id))?;
+ edit.into_model()
}
pub fn delete_release_handler(&self, id: &Uuid, editgroup_id: Option<Uuid>, conn: &DbConn) -> Result<EntityEdit> {
- unimplemented!()
+ let edit_context = make_edit_context(conn, editgroup_id.map(|u| FatCatId::from_uuid(&u)))?;
+ let edit = ReleaseEntity::db_delete(conn, &edit_context, FatCatId::from_uuid(id))?;
+ edit.into_model()
}
pub fn create_work_handler(
@@ -1150,30 +600,33 @@ impl Server {
}
entity_batch_handler!(
- create_container_handler,
create_container_batch_handler,
ContainerEntity
);
entity_batch_handler!(
- create_creator_handler,
create_creator_batch_handler,
CreatorEntity
);
- entity_batch_handler!(create_file_handler, create_file_batch_handler, FileEntity);
+ entity_batch_handler!(create_file_batch_handler, FileEntity);
entity_batch_handler!(
- create_release_handler,
create_release_batch_handler,
ReleaseEntity
);
- entity_batch_handler!(create_work_handler, create_work_batch_handler, WorkEntity);
+ entity_batch_handler!(create_work_batch_handler, WorkEntity);
- entity_history_handler!(
- get_container_history_handler,
- ContainerEditRow,
- container_edit
- );
- entity_history_handler!(get_creator_history_handler, CreatorEditRow, creator_edit);
- entity_history_handler!(get_file_history_handler, FileEditRow, file_edit);
- entity_history_handler!(get_release_history_handler, ReleaseEditRow, release_edit);
- entity_history_handler!(get_work_history_handler, WorkEditRow, work_edit);
+ pub fn get_container_history_handler(&self, id: &Uuid, limit: Option<i64>, conn: &DbConn,) -> Result<Vec<EntityHistoryEntry>> {
+ ContainerEntity::db_get_history(conn, FatCatId::from_uuid(id), limit)
+ }
+ pub fn get_creator_history_handler(&self, id: &Uuid, limit: Option<i64>, conn: &DbConn,) -> Result<Vec<EntityHistoryEntry>> {
+ CreatorEntity::db_get_history(conn, FatCatId::from_uuid(id), limit)
+ }
+ pub fn get_file_history_handler(&self, id: &Uuid, limit: Option<i64>, conn: &DbConn,) -> Result<Vec<EntityHistoryEntry>> {
+ FileEntity::db_get_history(conn, FatCatId::from_uuid(id), limit)
+ }
+ pub fn get_release_history_handler(&self, id: &Uuid, limit: Option<i64>, conn: &DbConn,) -> Result<Vec<EntityHistoryEntry>> {
+ ReleaseEntity::db_get_history(conn, FatCatId::from_uuid(id), limit)
+ }
+ pub fn get_work_history_handler(&self, id: &Uuid, limit: Option<i64>, conn: &DbConn,) -> Result<Vec<EntityHistoryEntry>> {
+ WorkEntity::db_get_history(conn, FatCatId::from_uuid(id), limit)
+ }
}
diff --git a/rust/src/database_entity_crud.rs b/rust/src/database_entity_crud.rs
index e97e134b..b8ff195a 100644
--- a/rust/src/database_entity_crud.rs
+++ b/rust/src/database_entity_crud.rs
@@ -1,11 +1,13 @@
+use sha1::Sha1;
+use chrono;
use diesel::prelude::*;
use diesel::{self, insert_into};
use database_schema::*;
use database_models::*;
use errors::*;
use fatcat_api::models::*;
-use api_helpers::{FatCatId, DbConn};
+use api_helpers::*;
use uuid::Uuid;
use std::marker::Sized;
use std::str::FromStr;
@@ -55,7 +57,7 @@ pub trait EntityCrud where Self: Sized {
fn db_get_history(conn: &DbConn, ident: FatCatId, limit: Option<i64>) -> Result<Vec<EntityHistoryEntry>>;
// Entity-specific Methods
- fn from_row(conn: &DbConn, rev_row: Self::RevRow, ident_row: Option<Self::IdentRow>) -> Result<Self>;
+ fn db_from_row(conn: &DbConn, rev_row: Self::RevRow, ident_row: Option<Self::IdentRow>) -> Result<Self>;
fn db_insert_rev(&self, conn: &DbConn) -> Result<Uuid>;
fn db_insert_revs(conn: &DbConn, models: &[&Self]) -> Result<Vec<Uuid>>;
}
@@ -80,7 +82,7 @@ macro_rules! generic_db_get {
.inner_join($rev_table::table)
.first(conn)?;
- Self::from_row(conn, rev, Some(ident))
+ Self::db_from_row(conn, rev, Some(ident))
}
}
}
@@ -92,12 +94,13 @@ macro_rules! generic_db_get_rev {
.find(rev_id)
.first(conn)?;
- Self::from_row(conn, rev, None)
+ Self::db_from_row(conn, rev, None)
}
}
}
macro_rules! generic_db_create {
+ // TODO: this path should call generic_db_create_batch
($ident_table: ident, $edit_table: ident) => {
fn db_create(&self, conn: &DbConn, edit_context: &EditContext) -> Result<Self::EditRow> {
let rev_id = self.db_insert_rev(conn)?;
@@ -128,7 +131,7 @@ macro_rules! generic_db_create_batch {
is_live: edit_context.autoapprove,
redirect_id: None,
})
- .collect::<Vec<WorkIdentNewRow>>())
+ .collect::<Vec<Self::IdentNewRow>>())
.returning($ident_table::id)
.get_results(conn)?;
let edits: Vec<Self::EditRow> = insert_into($edit_table::table)
@@ -141,7 +144,7 @@ macro_rules! generic_db_create_batch {
prev_rev: None,
extra_json: edit_context.extra_json.clone(),
})
- .collect::<Vec<WorkEditNewRow>>())
+ .collect::<Vec<Self::EditNewRow>>())
.get_results(conn)?;
Ok(edits)
}
@@ -209,7 +212,7 @@ macro_rules! generic_db_delete {
macro_rules! generic_db_get_history {
($edit_table:ident) => {
fn db_get_history(conn: &DbConn, ident: FatCatId, limit: Option<i64>) -> Result<Vec<EntityHistoryEntry>> {
- let limit = limit.unwrap_or(50); // XXX: make a static
+ let limit = limit.unwrap_or(50); // TODO: make a static
let rows: Vec<(EditgroupRow, ChangelogRow, Self::EditRow)> = editgroup::table
.inner_join(changelog::table)
@@ -239,6 +242,613 @@ macro_rules! generic_db_insert_rev {
}
}
+impl EntityCrud for ContainerEntity {
+ type EditRow = ContainerEditRow;
+ type EditNewRow = ContainerEditNewRow;
+ type IdentRow = ContainerIdentRow;
+ type IdentNewRow = ContainerIdentNewRow;
+ type RevRow = ContainerRevRow;
+
+ generic_parse_editgroup_id!();
+ generic_db_get!(container_ident, container_rev);
+ generic_db_get_rev!(container_rev);
+ generic_db_create!(container_ident, container_edit);
+ generic_db_create_batch!(container_ident, container_edit);
+ generic_db_update!(container_ident, container_edit);
+ generic_db_delete!(container_ident, container_edit);
+ generic_db_get_history!(container_edit);
+ generic_db_insert_rev!();
+
+ fn db_from_row(_conn: &DbConn, rev_row: Self::RevRow, ident_row: Option<Self::IdentRow>) -> Result<Self> {
+
+ let (state, ident_id, redirect_id) = match ident_row {
+ Some(i) => (
+ Some(i.state().unwrap().shortname()),
+ Some(FatCatId::from_uuid(&i.id).to_string()),
+ i.redirect_id.map(|u| FatCatId::from_uuid(&u).to_string()),
+ ),
+ None => (None, None, None),
+ };
+
+ Ok(ContainerEntity {
+ issnl: rev_row.issnl,
+ wikidata_qid: rev_row.wikidata_qid,
+ publisher: rev_row.publisher,
+ name: rev_row.name,
+ abbrev: rev_row.abbrev,
+ coden: rev_row.coden,
+ state: state,
+ ident: ident_id,
+ revision: Some(rev_row.id.to_string()),
+ redirect: redirect_id,
+ extra: rev_row.extra_json,
+ editgroup_id: None,
+ })
+ }
+
+ fn db_insert_revs(conn: &DbConn, models: &[&Self]) -> Result<Vec<Uuid>> {
+
+ // first verify external identifier syntax
+ for entity in models {
+ if let Some(ref extid) = entity.wikidata_qid {
+ check_wikidata_qid(extid)?;
+ }
+ if let Some(ref extid) = entity.issnl {
+ check_issn(extid)?;
+ }
+ }
+
+ let rev_ids: Vec<Uuid> = insert_into(container_rev::table)
+ .values(models.iter()
+ .map(|model| ContainerRevNewRow {
+ name: model.name.clone(),
+ publisher: model.publisher.clone(),
+ issnl: model.issnl.clone(),
+ wikidata_qid: model.wikidata_qid.clone(),
+ abbrev: model.abbrev.clone(),
+ coden: model.coden.clone(),
+ extra_json: model.extra.clone()
+ })
+ .collect::<Vec<ContainerRevNewRow>>())
+ .returning(container_rev::id)
+ .get_results(conn)?;
+ Ok(rev_ids)
+ }
+}
+
+impl EntityCrud for CreatorEntity {
+ type EditRow = CreatorEditRow;
+ type EditNewRow = CreatorEditNewRow;
+ type IdentRow = CreatorIdentRow;
+ type IdentNewRow = CreatorIdentNewRow;
+ type RevRow = CreatorRevRow;
+
+ generic_parse_editgroup_id!();
+ generic_db_get!(creator_ident, creator_rev);
+ generic_db_get_rev!(creator_rev);
+ generic_db_create!(creator_ident, creator_edit);
+ generic_db_create_batch!(creator_ident, creator_edit);
+ generic_db_update!(creator_ident, creator_edit);
+ generic_db_delete!(creator_ident, creator_edit);
+ generic_db_get_history!(creator_edit);
+ generic_db_insert_rev!();
+
+ fn db_from_row(_conn: &DbConn, rev_row: Self::RevRow, ident_row: Option<Self::IdentRow>) -> Result<Self> {
+ let (state, ident_id, redirect_id) = match ident_row {
+ Some(i) => (
+ Some(i.state().unwrap().shortname()),
+ Some(FatCatId::from_uuid(&i.id).to_string()),
+ i.redirect_id.map(|u| FatCatId::from_uuid(&u).to_string()),
+ ),
+ None => (None, None, None),
+ };
+ Ok(CreatorEntity {
+ display_name: rev_row.display_name,
+ given_name: rev_row.given_name,
+ surname: rev_row.surname,
+ orcid: rev_row.orcid,
+ wikidata_qid: rev_row.wikidata_qid,
+ state: state,
+ ident: ident_id,
+ revision: Some(rev_row.id.to_string()),
+ redirect: redirect_id,
+ editgroup_id: None,
+ extra: rev_row.extra_json,
+ })
+ }
+
+ fn db_insert_revs(conn: &DbConn, models: &[&Self]) -> Result<Vec<Uuid>> {
+
+ // first verify external identifier syntax
+ for entity in models {
+ if let Some(ref extid) = entity.orcid {
+ check_orcid(extid)?;
+ }
+ if let Some(ref extid) = entity.wikidata_qid {
+ check_wikidata_qid(extid)?;
+ }
+ }
+
+ let rev_ids: Vec<Uuid> = insert_into(creator_rev::table)
+ .values(models.iter()
+ .map(|model| CreatorRevNewRow {
+ display_name: model.display_name.clone(),
+ given_name: model.given_name.clone(),
+ surname: model.surname.clone(),
+ orcid: model.orcid.clone(),
+ wikidata_qid: model.wikidata_qid.clone(),
+ extra_json: model.extra.clone()
+ })
+ .collect::<Vec<CreatorRevNewRow>>())
+ .returning(creator_rev::id)
+ .get_results(conn)?;
+ Ok(rev_ids)
+ }
+}
+
+impl EntityCrud for FileEntity {
+ type EditRow = FileEditRow;
+ type EditNewRow = FileEditNewRow;
+ type IdentRow = FileIdentRow;
+ type IdentNewRow = FileIdentNewRow;
+ type RevRow = FileRevRow;
+
+ generic_parse_editgroup_id!();
+ generic_db_get!(file_ident, file_rev);
+ generic_db_get_rev!(file_rev);
+ generic_db_create!(file_ident, file_edit);
+ generic_db_create_batch!(file_ident, file_edit);
+ generic_db_update!(file_ident, file_edit);
+ generic_db_delete!(file_ident, file_edit);
+ generic_db_get_history!(file_edit);
+ generic_db_insert_rev!();
+
+ fn db_from_row(conn: &DbConn, rev_row: Self::RevRow, ident_row: Option<Self::IdentRow>) -> Result<Self> {
+ let (state, ident_id, redirect_id) = match ident_row {
+ Some(i) => (
+ Some(i.state().unwrap().shortname()),
+ Some(FatCatId::from_uuid(&i.id).to_string()),
+ i.redirect_id.map(|u| FatCatId::from_uuid(&u).to_string()),
+ ),
+ None => (None, None, None),
+ };
+
+ let releases: Vec<FatCatId> = file_release::table
+ .filter(file_release::file_rev.eq(rev_row.id))
+ .get_results(conn)?
+ .into_iter()
+ .map(|r: FileReleaseRow| FatCatId::from_uuid(&r.target_release_ident_id))
+ .collect();
+
+ let urls: Vec<FileEntityUrls> = file_rev_url::table
+ .filter(file_rev_url::file_rev.eq(rev_row.id))
+ .get_results(conn)?
+ .into_iter()
+ .map(|r: FileRevUrlRow| FileEntityUrls {
+ rel: r.rel,
+ url: r.url,
+ })
+ .collect();
+
+ Ok(FileEntity {
+ sha1: rev_row.sha1,
+ sha256: rev_row.sha256,
+ md5: rev_row.md5,
+ size: rev_row.size.map(|v| v as i64),
+ urls: Some(urls),
+ mimetype: rev_row.mimetype,
+ releases: Some(releases.iter().map(|fcid| fcid.to_string()).collect()),
+ state: state,
+ ident: ident_id,
+ revision: Some(rev_row.id.to_string()),
+ redirect: redirect_id,
+ editgroup_id: None,
+ extra: rev_row.extra_json,
+ })
+ }
+
+ fn db_insert_revs(conn: &DbConn, models: &[&Self]) -> Result<Vec<Uuid>> {
+
+ let rev_ids: Vec<Uuid> = insert_into(file_rev::table)
+ .values(models.iter()
+ .map(|model| FileRevNewRow {
+ size: model.size,
+ sha1: model.sha1.clone(),
+ sha256: model.sha256.clone(),
+ md5: model.md5.clone(),
+ mimetype: model.mimetype.clone(),
+ extra_json: model.extra.clone()
+ })
+ .collect::<Vec<FileRevNewRow>>())
+ .returning(file_rev::id)
+ .get_results(conn)?;
+
+ let mut file_release_rows: Vec<FileReleaseRow> = vec![];
+ let mut file_url_rows: Vec<FileRevUrlNewRow> = vec![];
+
+ for (model, rev_id) in models.iter().zip(rev_ids.iter()) {
+ match &model.releases {
+ None => (),
+ Some(release_list) => {
+ let these_release_rows: Vec<FileReleaseRow> = release_list
+ .iter()
+ .map(|r| FileReleaseRow {
+ file_rev: rev_id.clone(),
+ target_release_ident_id: fcid2uuid(r)
+ // XXX: shouldn't expect
+ .expect("invalid fatcat identifier"),
+ })
+ .collect();
+ file_release_rows.extend(these_release_rows);
+ }
+ };
+
+ match &model.urls {
+ None => (),
+ Some(url_list) => {
+ let these_url_rows: Vec<FileRevUrlNewRow> = url_list
+ .into_iter()
+ .map(|u| FileRevUrlNewRow {
+ file_rev: rev_id.clone(),
+ rel: u.rel.clone(),
+ url: u.url.clone(),
+ })
+ .collect();
+ file_url_rows.extend(these_url_rows);
+ }
+ };
+ }
+
+ if !file_release_rows.is_empty() {
+ // TODO: shouldn't it be "file_rev_release"?
+ insert_into(file_release::table)
+ .values(file_release_rows)
+ .execute(conn)?;
+ }
+
+ if !file_url_rows.is_empty() {
+ insert_into(file_rev_url::table)
+ .values(file_url_rows)
+ .execute(conn)?;
+ }
+
+ Ok(rev_ids)
+ }
+}
+
+impl EntityCrud for ReleaseEntity {
+ type EditRow = ReleaseEditRow;
+ type EditNewRow = ReleaseEditNewRow;
+ type IdentRow = ReleaseIdentRow;
+ type IdentNewRow = ReleaseIdentNewRow;
+ type RevRow = ReleaseRevRow;
+
+ generic_parse_editgroup_id!();
+ generic_db_get!(release_ident, release_rev);
+ generic_db_get_rev!(release_rev);
+ //generic_db_create!(release_ident, release_edit);
+ //generic_db_create_batch!(release_ident, release_edit);
+ generic_db_update!(release_ident, release_edit);
+ generic_db_delete!(release_ident, release_edit);
+ generic_db_get_history!(release_edit);
+ generic_db_insert_rev!();
+
+ fn db_create(&self, conn: &DbConn, edit_context: &EditContext) -> Result<Self::EditRow> {
+ let mut edits = Self::db_create_batch(conn, edit_context, &vec![self])?;
+ // probably a more elegant way to destroy the vec and take first element
+ Ok(edits.pop().unwrap())
+ }
+
+ fn db_create_batch(conn: &DbConn, edit_context: &EditContext, models: &[&Self]) -> Result<Vec<Self::EditRow>> {
+ // This isn't the generic implementation because we need to create Work entities for each
+ // of the release entities passed (at least in the common case)
+
+ // Generate the set of new work entities to insert (usually one for each release, but some
+ // releases might be pointed to a work already)
+ let mut new_work_models: Vec<&WorkEntity> = vec![];
+ for entity in models {
+ if entity.work_id.is_none() {
+ new_work_models.push(&WorkEntity {
+ ident: None,
+ revision: None,
+ redirect: None,
+ state: None,
+ editgroup_id: None,
+ extra: None,
+ });
+ };
+ }
+
+ // create the works, then pluck the list of idents from the result
+ let new_work_edits = WorkEntity::db_create_batch(conn, edit_context, new_work_models.as_slice())?;
+ let mut new_work_ids: Vec<Uuid> = new_work_edits.iter().map(|edit| edit.ident_id).collect();
+
+ // Copy all the release models, and ensure that each has work_id set, using the new work
+ // idents. There should be one new work ident for each release missing one.
+ let models_with_work_ids: Vec<Self> = models.iter().map(|model| {
+ let mut model = (*model).clone();
+ if model.work_id.is_none() {
+ model.work_id = Some(FatCatId::from_uuid(&new_work_ids.pop().unwrap()).to_string())
+ }
+ model
+ }).collect();
+ let model_refs: Vec<&Self> = models_with_work_ids.iter().map(|s| s).collect();
+ let models = model_refs.as_slice();
+
+ // The rest here is copy/pasta from the generic (how to avoid copypasta?)
+ let rev_ids: Vec<Uuid> = Self::db_insert_revs(conn, models)?;
+ let ident_ids: Vec<Uuid> = insert_into(release_ident::table)
+ .values(rev_ids.iter()
+ .map(|rev_id| Self::IdentNewRow {
+ rev_id: Some(rev_id.clone()),
+ is_live: edit_context.autoapprove,
+ redirect_id: None,
+ })
+ .collect::<Vec<Self::IdentNewRow>>())
+ .returning(release_ident::id)
+ .get_results(conn)?;
+ let edits: Vec<Self::EditRow> = insert_into(release_edit::table)
+ .values(rev_ids.into_iter().zip(ident_ids.into_iter())
+ .map(|(rev_id, ident_id)| Self::EditNewRow {
+ editgroup_id: edit_context.editgroup_id.to_uuid(),
+ rev_id: Some(rev_id),
+ ident_id: ident_id,
+ redirect_id: None,
+ prev_rev: None,
+ extra_json: edit_context.extra_json.clone(),
+ })
+ .collect::<Vec<Self::EditNewRow>>())
+ .get_results(conn)?;
+ Ok(edits)
+ }
+
+ fn db_from_row(conn: &DbConn, rev_row: Self::RevRow, ident_row: Option<Self::IdentRow>) -> Result<Self> {
+ let (state, ident_id, redirect_id) = match ident_row {
+ Some(i) => (
+ Some(i.state().unwrap().shortname()),
+ Some(FatCatId::from_uuid(&i.id).to_string()),
+ i.redirect_id.map(|u| FatCatId::from_uuid(&u).to_string()),
+ ),
+ None => (None, None, None),
+ };
+
+ let refs: Vec<ReleaseRef> = release_ref::table
+ .filter(release_ref::release_rev.eq(rev_row.id))
+ .order(release_ref::index_val.asc())
+ .get_results(conn)
+ .expect("fetch release refs")
+ .into_iter()
+ .map(|r: ReleaseRefRow| ReleaseRef {
+ index: r.index_val,
+ key: r.key,
+ extra: r.extra_json,
+ container_title: r.container_title,
+ year: r.year,
+ title: r.title,
+ locator: r.locator,
+ target_release_id: r.target_release_ident_id.map(|v| FatCatId::from_uuid(&v).to_string()),
+ })
+ .collect();
+
+ let contribs: Vec<ReleaseContrib> = release_contrib::table
+ .filter(release_contrib::release_rev.eq(rev_row.id))
+ .order((
+ release_contrib::role.asc(),
+ release_contrib::index_val.asc(),
+ ))
+ .get_results(conn)
+ .expect("fetch release refs")
+ .into_iter()
+ .map(|c: ReleaseContribRow| ReleaseContrib {
+ index: c.index_val,
+ raw_name: c.raw_name,
+ role: c.role,
+ extra: c.extra_json,
+ creator_id: c.creator_ident_id.map(|v| FatCatId::from_uuid(&v).to_string()),
+ creator: None,
+ })
+ .collect();
+
+ let abstracts: Vec<ReleaseEntityAbstracts> = release_rev_abstract::table
+ .inner_join(abstracts::table)
+ .filter(release_rev_abstract::release_rev.eq(rev_row.id))
+ .get_results(conn)?
+ .into_iter()
+ .map(
+ |r: (ReleaseRevAbstractRow, AbstractsRow)| ReleaseEntityAbstracts {
+ sha1: Some(r.0.abstract_sha1),
+ mimetype: r.0.mimetype,
+ lang: r.0.lang,
+ content: Some(r.1.content),
+ },
+ )
+ .collect();
+
+ Ok(ReleaseEntity {
+ title: rev_row.title,
+ release_type: rev_row.release_type,
+ release_status: rev_row.release_status,
+ release_date: rev_row.release_date
+ .map(|v| chrono::DateTime::from_utc(v.and_hms(0, 0, 0), chrono::Utc)),
+ doi: rev_row.doi,
+ pmid: rev_row.pmid,
+ pmcid: rev_row.pmcid,
+ isbn13: rev_row.isbn13,
+ core_id: rev_row.core_id,
+ wikidata_qid: rev_row.wikidata_qid,
+ volume: rev_row.volume,
+ issue: rev_row.issue,
+ pages: rev_row.pages,
+ files: None,
+ container: None,
+ container_id: rev_row.container_ident_id.map(|u| FatCatId::from_uuid(&u).to_string()),
+ publisher: rev_row.publisher,
+ language: rev_row.language,
+ work_id: Some(FatCatId::from_uuid(&rev_row.work_ident_id).to_string()),
+ refs: Some(refs),
+ contribs: Some(contribs),
+ abstracts: Some(abstracts),
+ state: state,
+ ident: ident_id,
+ revision: Some(rev_row.id.to_string()),
+ redirect: redirect_id,
+ editgroup_id: None,
+ extra: rev_row.extra_json,
+ })
+ }
+
+ fn db_insert_revs(conn: &DbConn, models: &[&Self]) -> Result<Vec<Uuid>> {
+
+ // first verify external identifier syntax
+ for entity in models {
+ if let Some(ref extid) = entity.doi {
+ check_doi(extid)?;
+ }
+ if let Some(ref extid) = entity.pmid {
+ check_pmid(extid)?;
+ }
+ if let Some(ref extid) = entity.pmcid {
+ check_pmcid(extid)?;
+ }
+ if let Some(ref extid) = entity.wikidata_qid {
+ check_wikidata_qid(extid)?;
+ }
+ }
+
+ let rev_ids: Vec<Uuid> = insert_into(release_rev::table)
+ .values(models.iter()
+ .map(|model| ReleaseRevNewRow {
+ title: model.title.clone(),
+ release_type: model.release_type.clone(),
+ release_status: model.release_status.clone(),
+ release_date: model.release_date.map(|v| v.naive_utc().date()),
+ doi: model.doi.clone(),
+ pmid: model.pmid.clone(),
+ pmcid: model.pmcid.clone(),
+ wikidata_qid: model.wikidata_qid.clone(),
+ isbn13: model.isbn13.clone(),
+ core_id: model.core_id.clone(),
+ volume: model.volume.clone(),
+ issue: model.issue.clone(),
+ pages: model.pages.clone(),
+ work_ident_id: model.work_id
+ .clone()
+ .map(|s| FatCatId::from_str(&s).expect("invalid fatcat identifier").to_uuid())
+ .expect("release_revs must have a work_id by the time they are inserted; this is an internal soundness error"),
+ container_ident_id: model.container_id
+ .clone()
+ .map(|s| FatCatId::from_str(&s).expect("invalid fatcat identifier").to_uuid()),
+ publisher: model.publisher.clone(),
+ language: model.language.clone(),
+ extra_json: model.extra.clone()
+ })
+ .collect::<Vec<ReleaseRevNewRow>>())
+ .returning(release_rev::id)
+ .get_results(conn)?;
+
+ let mut release_ref_rows: Vec<ReleaseRefNewRow> = vec![];
+ let mut release_contrib_rows: Vec<ReleaseContribNewRow> = vec![];
+
+ for (model, rev_id) in models.iter().zip(rev_ids.iter()) {
+ match &model.refs {
+ None => (),
+ Some(ref_list) => {
+ let these_ref_rows: Vec<ReleaseRefNewRow> = ref_list
+ .iter()
+ .map(|r| ReleaseRefNewRow {
+ release_rev: rev_id.clone(),
+ target_release_ident_id: r.target_release_id
+ .clone()
+ .map(|v| fcid2uuid(&v).expect("valid fatcat identifier")),
+ index_val: r.index,
+ key: r.key.clone(),
+ container_title: r.container_title.clone(),
+ year: r.year,
+ title: r.title.clone(),
+ locator: r.locator.clone(),
+ extra_json: r.extra.clone(),
+ })
+ .collect();
+ release_ref_rows.extend(these_ref_rows);
+ }
+ };
+
+ match &model.contribs {
+ None => (),
+ Some(contrib_list) => {
+ let these_contrib_rows: Vec<ReleaseContribNewRow> = contrib_list
+ .iter()
+ .map(|c| ReleaseContribNewRow {
+ release_rev: rev_id.clone(),
+ creator_ident_id: c.creator_id
+ .clone()
+ // XXX: shouldn't have these expects
+ .map(|v| fcid2uuid(&v).expect("valid fatcat identifier")),
+ raw_name: c.raw_name.clone(),
+ index_val: c.index,
+ role: c.role.clone(),
+ extra_json: c.extra.clone(),
+ })
+ .collect();
+ release_contrib_rows.extend(these_contrib_rows);
+ }
+ };
+
+ // TODO: this part still isn't parallelized
+ if let Some(abstract_list) = &model.abstracts {
+ // For rows that specify content, we need to insert the abstract if it doesn't exist
+ // already
+ let new_abstracts: Vec<AbstractsRow> = abstract_list
+ .iter()
+ .filter(|ea| ea.content.is_some())
+ .map(|c| AbstractsRow {
+ sha1: Sha1::from(c.content.clone().unwrap()).hexdigest(),
+ content: c.content.clone().unwrap(),
+ })
+ .collect();
+ if !new_abstracts.is_empty() {
+ // Sort of an "upsert"; only inserts new abstract rows if they don't already exist
+ insert_into(abstracts::table)
+ .values(&new_abstracts)
+ .on_conflict(abstracts::sha1)
+ .do_nothing()
+ .execute(conn)?;
+ }
+ let release_abstract_rows: Vec<ReleaseRevAbstractNewRow> = abstract_list
+ .into_iter()
+ .map(|c| ReleaseRevAbstractNewRow {
+ release_rev: rev_id.clone(),
+ abstract_sha1: match c.content {
+ Some(ref content) => Sha1::from(content).hexdigest(),
+ // XXX: shouldn't have these expects
+ None => c.sha1.clone().expect("either abstract_sha1 or content is required"),
+ },
+ lang: c.lang.clone(),
+ mimetype: c.mimetype.clone(),
+ })
+ .collect();
+ insert_into(release_rev_abstract::table)
+ .values(release_abstract_rows)
+ .execute(conn)?;
+ }
+ }
+
+ if !release_ref_rows.is_empty() {
+ insert_into(release_ref::table)
+ .values(release_ref_rows)
+ .execute(conn)?;
+ }
+
+ if !release_contrib_rows.is_empty() {
+ insert_into(release_contrib::table)
+ .values(release_contrib_rows)
+ .execute(conn)?;
+ }
+
+ Ok(rev_ids)
+ }
+}
+
impl EntityCrud for WorkEntity {
type EditRow = WorkEditRow;
type EditNewRow = WorkEditNewRow;
@@ -256,7 +866,7 @@ impl EntityCrud for WorkEntity {
generic_db_get_history!(work_edit);
generic_db_insert_rev!();
- fn from_row(conn: &DbConn, rev_row: Self::RevRow, ident_row: Option<Self::IdentRow>) -> Result<Self> {
+ fn db_from_row(_conn: &DbConn, rev_row: Self::RevRow, ident_row: Option<Self::IdentRow>) -> Result<Self> {
let (state, ident_id, redirect_id) = match ident_row {
Some(i) => (
diff --git a/rust/src/database_models.rs b/rust/src/database_models.rs
index 14215a3c..2d6788eb 100644
--- a/rust/src/database_models.rs
+++ b/rust/src/database_models.rs
@@ -125,6 +125,18 @@ pub struct ContainerRevRow {
pub coden: Option<String>,
}
+#[derive(Debug, Associations, AsChangeset, Insertable)]
+#[table_name = "container_rev"]
+pub struct ContainerRevNewRow {
+ pub extra_json: Option<serde_json::Value>,
+ pub name: String,
+ pub publisher: Option<String>,
+ pub issnl: Option<String>,
+ pub wikidata_qid: Option<String>,
+ pub abbrev: Option<String>,
+ pub coden: Option<String>,
+}
+
entity_structs!(
"container_edit",
ContainerEditRow,
@@ -146,6 +158,17 @@ pub struct CreatorRevRow {
pub wikidata_qid: Option<String>,
}
+#[derive(Debug, Associations, AsChangeset, Insertable)]
+#[table_name = "creator_rev"]
+pub struct CreatorRevNewRow {
+ pub extra_json: Option<serde_json::Value>,
+ pub display_name: String,
+ pub given_name: Option<String>,
+ pub surname: Option<String>,
+ pub orcid: Option<String>,
+ pub wikidata_qid: Option<String>,
+}
+
entity_structs!(
"creator_edit",
CreatorEditRow,
@@ -184,6 +207,17 @@ pub struct FileRevRow {
pub mimetype: Option<String>,
}
+#[derive(Debug, Associations, AsChangeset, Insertable)]
+#[table_name = "file_rev"]
+pub struct FileRevNewRow {
+ pub extra_json: Option<serde_json::Value>,
+ pub size: Option<i64>,
+ pub sha1: Option<String>,
+ pub sha256: Option<String>,
+ pub md5: Option<String>,
+ pub mimetype: Option<String>,
+}
+
entity_structs!("file_edit", FileEditRow, FileEditNewRow, "file_ident", FileIdentRow, FileIdentNewRow);
#[derive(Debug, Queryable, Identifiable, Associations, AsChangeset)]
@@ -210,6 +244,29 @@ pub struct ReleaseRevRow {
pub language: Option<String>,
}
+#[derive(Debug, Associations, AsChangeset, Insertable)]
+#[table_name = "release_rev"]
+pub struct ReleaseRevNewRow {
+ pub extra_json: Option<serde_json::Value>,
+ pub work_ident_id: Uuid,
+ pub container_ident_id: Option<Uuid>,
+ pub title: String,
+ pub release_type: Option<String>,
+ pub release_status: Option<String>,
+ pub release_date: Option<chrono::NaiveDate>,
+ pub doi: Option<String>,
+ pub pmid: Option<String>,
+ pub pmcid: Option<String>,
+ pub wikidata_qid: Option<String>,
+ pub isbn13: Option<String>,
+ pub core_id: Option<String>,
+ pub volume: Option<String>,
+ pub issue: Option<String>,
+ pub pages: Option<String>,
+ pub publisher: Option<String>,
+ pub language: Option<String>,
+}
+
entity_structs!(
"release_edit",
ReleaseEditRow,