use api_helpers::uuid2fcid; use chrono; use database_schema::*; use errors::*; use fatcat_api::models::{ChangelogEntry, Editgroup, EntityEdit}; use serde_json; use uuid::Uuid; // Ugh. I thought the whole point was to *not* do this, but: // https://github.com/diesel-rs/diesel/issues/1589 pub enum EntityState { WorkInProgress, Active(Uuid), Redirect(Uuid, Uuid), Deleted, } impl EntityState { pub fn shortname(&self) -> String { match self { EntityState::WorkInProgress => "wip", EntityState::Active(_) => "active", EntityState::Redirect(_, _) => "redirect", EntityState::Deleted => "deleted", }.to_string() } } pub trait EntityIdentRow { fn state(&self) -> Result<EntityState>; } pub trait EntityEditRow { fn into_model(self) -> Result<EntityEdit>; } // Helper for constructing tables macro_rules! entity_structs { ($edit_table:expr, $edit_struct:ident, $ident_table:expr, $ident_struct:ident) => { #[derive(Debug, Queryable, Identifiable, Associations, AsChangeset, QueryableByName)] #[table_name = $edit_table] pub struct $edit_struct { pub id: i64, pub editgroup_id: Uuid, pub updated: chrono::NaiveDateTime, pub ident_id: Uuid, pub rev_id: Option<Uuid>, pub redirect_id: Option<Uuid>, pub prev_rev: Option<Uuid>, pub extra_json: Option<serde_json::Value>, } impl EntityEditRow for $edit_struct { /// Go from a row (SQL model) to an API model fn into_model(self) -> Result<EntityEdit> { Ok(EntityEdit { editgroup_id: uuid2fcid(&self.editgroup_id), revision: self.rev_id.map(|v| v.to_string()), redirect_ident: self.redirect_id.map(|v| uuid2fcid(&v)), prev_revision: self.prev_rev.map(|v| v.to_string()), ident: uuid2fcid(&self.ident_id), edit_id: self.id, extra: self.extra_json, }) } } #[derive(Debug, Queryable, Identifiable, Associations, AsChangeset)] #[table_name = $ident_table] pub struct $ident_struct { pub id: Uuid, pub is_live: bool, pub rev_id: Option<Uuid>, pub redirect_id: Option<Uuid>, } impl EntityIdentRow for $ident_struct { fn state(&self) -> Result<EntityState> { if !self.is_live { return Ok(EntityState::WorkInProgress); } match (self.redirect_id, self.rev_id) { (None, None) => Ok(EntityState::Deleted), (Some(redir), Some(rev)) => Ok(EntityState::Redirect(redir, rev)), (None, Some(rev)) => Ok(EntityState::Active(rev)), _ => bail!("Invalid EntityIdentRow state"), } } } }; } #[derive(Debug, Queryable, Identifiable, Associations, AsChangeset)] #[table_name = "container_rev"] pub struct ContainerRevRow { pub id: Uuid, 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, "container_ident", ContainerIdentRow ); #[derive(Debug, Queryable, Identifiable, Associations, AsChangeset)] #[table_name = "creator_rev"] pub struct CreatorRevRow { pub id: Uuid, 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, "creator_ident", CreatorIdentRow ); #[derive(Debug, Queryable, Identifiable, Associations, AsChangeset)] #[table_name = "file_rev_url"] pub struct FileRevUrlRow { pub id: i64, pub file_rev: Uuid, pub rel: String, pub url: String, } #[derive(Debug, Queryable, Associations, AsChangeset, Insertable)] #[table_name = "file_rev_url"] pub struct FileRevUrlNewRow { pub file_rev: Uuid, pub rel: String, pub url: String, } #[derive(Debug, Queryable, Identifiable, Associations, AsChangeset)] #[table_name = "file_rev"] pub struct FileRevRow { pub id: Uuid, 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, "file_ident", FileIdentRow); #[derive(Debug, Queryable, Identifiable, Associations, AsChangeset)] #[table_name = "release_rev"] pub struct ReleaseRevRow { pub id: Uuid, 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, "release_ident", ReleaseIdentRow ); #[derive(Debug, Queryable, Identifiable, Associations, AsChangeset)] #[table_name = "work_rev"] pub struct WorkRevRow { pub id: Uuid, pub extra_json: Option<serde_json::Value>, } entity_structs!("work_edit", WorkEditRow, "work_ident", WorkIdentRow); #[derive(Debug, Queryable, Identifiable, Associations, AsChangeset)] #[table_name = "release_rev_abstract"] pub struct ReleaseRevAbstractRow { pub id: i64, pub release_rev: Uuid, pub abstract_sha1: String, pub mimetype: Option<String>, pub lang: Option<String>, } #[derive(Debug, Queryable, Associations, AsChangeset, Insertable)] #[table_name = "release_rev_abstract"] pub struct ReleaseRevAbstractNewRow { pub release_rev: Uuid, pub abstract_sha1: String, pub mimetype: Option<String>, pub lang: Option<String>, } #[derive(Debug, Queryable, Identifiable, Associations, AsChangeset)] #[table_name = "release_contrib"] pub struct ReleaseContribRow { pub id: i64, pub release_rev: Uuid, pub creator_ident_id: Option<Uuid>, pub raw_name: Option<String>, pub role: Option<String>, pub index_val: Option<i64>, pub extra_json: Option<serde_json::Value>, } #[derive(Debug, Insertable)] #[table_name = "release_contrib"] pub struct ReleaseContribNewRow { pub release_rev: Uuid, pub creator_ident_id: Option<Uuid>, pub raw_name: Option<String>, pub role: Option<String>, pub index_val: Option<i64>, pub extra_json: Option<serde_json::Value>, } #[derive(Debug, Queryable, Identifiable, 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<i64>, pub key: Option<String>, pub extra_json: Option<serde_json::Value>, pub container_title: Option<String>, pub year: Option<i64>, pub title: Option<String>, pub locator: Option<String>, } #[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<i64>, pub key: Option<String>, pub extra_json: Option<serde_json::Value>, pub container_title: Option<String>, pub year: Option<i64>, pub title: Option<String>, pub locator: Option<String>, } #[derive(Debug, Queryable, Insertable, Associations, AsChangeset)] #[table_name = "file_release"] pub struct FileReleaseRow { pub file_rev: Uuid, pub target_release_ident_id: Uuid, } #[derive(Debug, Queryable, Insertable, Associations, AsChangeset)] #[table_name = "abstracts"] pub struct AbstractsRow { pub sha1: String, pub content: String, } #[derive(Debug, Queryable, Identifiable, Associations, AsChangeset)] #[table_name = "editgroup"] pub struct EditgroupRow { pub id: Uuid, pub editor_id: Uuid, pub created: chrono::NaiveDateTime, pub extra_json: Option<serde_json::Value>, pub description: Option<String>, } impl EditgroupRow { /// Returns an Edigroup API model *without* the entity edits actually populated. Useful for, /// eg, entity history queries (where we already have the entity edit we want) pub fn into_model_partial(self) -> Editgroup { Editgroup { id: Some(uuid2fcid(&self.id)), editor_id: uuid2fcid(&self.editor_id), description: self.description, extra: self.extra_json, edits: None, } } } #[derive(Debug, Queryable, Identifiable, Associations, AsChangeset)] #[table_name = "editor"] pub struct EditorRow { pub id: Uuid, pub username: String, pub is_admin: bool, pub registered: chrono::NaiveDateTime, pub active_editgroup_id: Option<Uuid>, } #[derive(Debug, Queryable, Identifiable, Associations, AsChangeset)] #[table_name = "changelog"] pub struct ChangelogRow { pub id: i64, pub editgroup_id: Uuid, pub timestamp: chrono::NaiveDateTime, } impl ChangelogRow { pub fn into_model(self) -> ChangelogEntry { ChangelogEntry { index: self.id, editgroup_id: uuid2fcid(&self.editgroup_id), editgroup: None, timestamp: chrono::DateTime::from_utc(self.timestamp, chrono::Utc), } } }