#![allow(proc_macro_derive_resolution_fallback)] use crate::database_schema::*; use crate::errors::*; use crate::identifiers::uuid2fcid; use chrono::Utc; use fatcat_api_spec::models::{ ChangelogEntry, Editgroup, EditgroupAnnotation, Editor, EntityEdit, ReleaseRef, }; 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 #[derive(Clone, Copy, Debug, PartialEq)] pub enum EntityState { WorkInProgress, Active(Uuid), Redirect(Uuid, Option), 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; } pub trait EntityEditRow { fn into_model(self) -> Result; } // Helper for constructing tables macro_rules! entity_structs { ( $edit_table:expr, $edit_struct:ident, $edit_new_struct:ident, $ident_table:expr, $ident_struct:ident, $ident_new_struct:ident ) => { #[derive(Debug, Queryable, Identifiable, Associations, AsChangeset, QueryableByName)] #[table_name = $edit_table] pub struct $edit_struct { pub id: Uuid, pub editgroup_id: Uuid, pub updated: chrono::NaiveDateTime, pub ident_id: Uuid, pub rev_id: Option, pub redirect_id: Option, pub prev_rev: Option, pub extra_json: Option, } #[derive(Debug, Associations, AsChangeset, QueryableByName, Insertable)] #[table_name = $edit_table] pub struct $edit_new_struct { pub editgroup_id: Uuid, pub ident_id: Uuid, pub rev_id: Option, pub redirect_id: Option, pub prev_rev: Option, pub extra_json: Option, } impl EntityEditRow for $edit_struct { /// Go from a row (SQL model) to an API model fn into_model(self) -> Result { 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.to_string(), 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, pub redirect_id: Option, } #[derive(Debug, Associations, AsChangeset, Insertable)] #[table_name = $ident_table] pub struct $ident_new_struct { pub is_live: bool, pub rev_id: Option, pub redirect_id: Option, } impl EntityIdentRow for $ident_struct { fn state(&self) -> Result { if !self.is_live { return Ok(EntityState::WorkInProgress); } match (self.redirect_id, self.rev_id) { (None, None) => Ok(EntityState::Deleted), (Some(redir), 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, pub name: String, pub container_type: Option, pub publisher: Option, pub issnl: Option, pub wikidata_qid: Option, } #[derive(Debug, Associations, AsChangeset, Insertable)] #[table_name = "container_rev"] pub struct ContainerRevNewRow { pub extra_json: Option, pub name: String, pub container_type: Option, pub publisher: Option, pub issnl: Option, pub wikidata_qid: Option, } entity_structs!( "container_edit", ContainerEditRow, ContainerEditNewRow, "container_ident", ContainerIdentRow, ContainerIdentNewRow ); #[derive(Debug, Queryable, Identifiable, Associations, AsChangeset)] #[table_name = "creator_rev"] pub struct CreatorRevRow { pub id: Uuid, pub extra_json: Option, pub display_name: String, pub given_name: Option, pub surname: Option, pub orcid: Option, pub wikidata_qid: Option, } #[derive(Debug, Associations, AsChangeset, Insertable)] #[table_name = "creator_rev"] pub struct CreatorRevNewRow { pub extra_json: Option, pub display_name: String, pub given_name: Option, pub surname: Option, pub orcid: Option, pub wikidata_qid: Option, } entity_structs!( "creator_edit", CreatorEditRow, CreatorEditNewRow, "creator_ident", CreatorIdentRow, CreatorIdentNewRow ); #[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, pub size_bytes: Option, pub sha1: Option, pub sha256: Option, pub md5: Option, pub mimetype: Option, } #[derive(Debug, Associations, AsChangeset, Insertable)] #[table_name = "file_rev"] pub struct FileRevNewRow { pub extra_json: Option, pub size_bytes: Option, pub sha1: Option, pub sha256: Option, pub md5: Option, pub mimetype: Option, } entity_structs!( "file_edit", FileEditRow, FileEditNewRow, "file_ident", FileIdentRow, FileIdentNewRow ); #[derive(Debug, Queryable, Identifiable, Associations, AsChangeset)] #[table_name = "fileset_rev_file"] pub struct FilesetRevFileRow { pub id: i64, pub fileset_rev: Uuid, pub path_name: String, pub size_bytes: i64, pub md5: Option, pub sha1: Option, pub sha256: Option, pub extra_json: Option, } #[derive(Debug, Queryable, Associations, AsChangeset, Insertable)] #[table_name = "fileset_rev_file"] pub struct FilesetRevFileNewRow { pub fileset_rev: Uuid, pub path_name: String, pub size_bytes: i64, pub md5: Option, pub sha1: Option, pub sha256: Option, pub extra_json: Option, } #[derive(Debug, Queryable, Identifiable, Associations, AsChangeset)] #[table_name = "fileset_rev_url"] pub struct FilesetRevUrlRow { pub id: i64, pub fileset_rev: Uuid, pub rel: String, pub url: String, } #[derive(Debug, Queryable, Associations, AsChangeset, Insertable)] #[table_name = "fileset_rev_url"] pub struct FilesetRevUrlNewRow { pub fileset_rev: Uuid, pub rel: String, pub url: String, } #[derive(Debug, Queryable, Identifiable, Associations, AsChangeset)] #[table_name = "fileset_rev"] pub struct FilesetRevRow { pub id: Uuid, pub extra_json: Option, } #[derive(Debug, Associations, AsChangeset, Insertable)] #[table_name = "fileset_rev"] pub struct FilesetRevNewRow { pub extra_json: Option, } entity_structs!( "fileset_edit", FilesetEditRow, FilesetEditNewRow, "fileset_ident", FilesetIdentRow, FilesetIdentNewRow ); #[derive(Debug, Queryable, Identifiable, Associations, AsChangeset)] #[table_name = "webcapture_rev_cdx"] pub struct WebcaptureRevCdxRow { pub id: i64, pub webcapture_rev: Uuid, pub surt: String, pub timestamp: chrono::DateTime, pub url: String, pub mimetype: Option, pub status_code: Option, pub sha1: String, pub sha256: Option, pub size_bytes: Option, } #[derive(Debug, Queryable, Associations, AsChangeset, Insertable)] #[table_name = "webcapture_rev_cdx"] pub struct WebcaptureRevCdxNewRow { pub webcapture_rev: Uuid, pub surt: String, pub timestamp: chrono::DateTime, pub url: String, pub mimetype: Option, pub status_code: Option, pub sha1: String, pub sha256: Option, pub size_bytes: Option, } #[derive(Debug, Queryable, Identifiable, Associations, AsChangeset)] #[table_name = "webcapture_rev_url"] pub struct WebcaptureRevUrlRow { pub id: i64, pub webcapture_rev: Uuid, pub rel: String, pub url: String, } #[derive(Debug, Queryable, Associations, AsChangeset, Insertable)] #[table_name = "webcapture_rev_url"] pub struct WebcaptureRevUrlNewRow { pub webcapture_rev: Uuid, pub rel: String, pub url: String, } #[derive(Debug, Queryable, Identifiable, Associations, AsChangeset)] #[table_name = "webcapture_rev"] pub struct WebcaptureRevRow { pub id: Uuid, pub extra_json: Option, pub original_url: String, pub timestamp: chrono::NaiveDateTime, } #[derive(Debug, Associations, AsChangeset, Insertable)] #[table_name = "webcapture_rev"] pub struct WebcaptureRevNewRow { pub extra_json: Option, pub original_url: String, pub timestamp: chrono::NaiveDateTime, } entity_structs!( "webcapture_edit", WebcaptureEditRow, WebcaptureEditNewRow, "webcapture_ident", WebcaptureIdentRow, WebcaptureIdentNewRow ); #[derive(Debug, Queryable, Identifiable, Associations, AsChangeset)] #[table_name = "release_rev"] pub struct ReleaseRevRow { pub id: Uuid, pub extra_json: Option, pub work_ident_id: Uuid, pub container_ident_id: Option, pub refs_blob_sha1: Option, pub title: String, pub original_title: Option, pub release_type: Option, pub release_stage: Option, pub release_date: Option, pub release_year: Option, pub doi: Option, pub pmid: Option, pub pmcid: Option, pub wikidata_qid: Option, pub core_id: Option, pub volume: Option, pub issue: Option, pub pages: Option, pub publisher: Option, pub language: Option, pub license_slug: Option, pub number: Option, pub version: Option, pub subtitle: Option, pub withdrawn_status: Option, pub withdrawn_date: Option, pub withdrawn_year: Option, } #[derive(Debug, Associations, AsChangeset, Insertable)] #[table_name = "release_rev"] pub struct ReleaseRevNewRow { pub extra_json: Option, pub work_ident_id: Uuid, pub container_ident_id: Option, pub refs_blob_sha1: Option, pub title: String, pub original_title: Option, pub release_type: Option, pub release_stage: Option, pub release_date: Option, pub release_year: Option, pub doi: Option, pub pmid: Option, pub pmcid: Option, pub wikidata_qid: Option, pub core_id: Option, pub volume: Option, pub issue: Option, pub pages: Option, pub publisher: Option, pub language: Option, pub license_slug: Option, pub number: Option, pub version: Option, pub subtitle: Option, pub withdrawn_status: Option, pub withdrawn_date: Option, pub withdrawn_year: Option, } #[derive(Debug, Queryable, Associations, AsChangeset, Insertable)] #[table_name = "release_rev_extid"] pub struct ReleaseExtidRow { pub release_rev: Uuid, pub extid_type: String, pub value: String, } entity_structs!( "release_edit", ReleaseEditRow, ReleaseEditNewRow, "release_ident", ReleaseIdentRow, ReleaseIdentNewRow ); #[derive(Debug, Queryable, Identifiable, Associations, AsChangeset)] #[table_name = "work_rev"] pub struct WorkRevRow { pub id: Uuid, pub extra_json: Option, } #[derive(Debug, Associations, AsChangeset, Insertable)] #[table_name = "work_rev"] pub struct WorkRevNewRow { pub extra_json: Option, } entity_structs!( "work_edit", WorkEditRow, WorkEditNewRow, "work_ident", WorkIdentRow, WorkIdentNewRow ); #[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, pub lang: Option, } #[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, pub lang: Option, } #[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, pub raw_name: Option, pub role: Option, pub raw_affiliation: Option, pub index_val: Option, pub extra_json: Option, pub given_name: Option, pub surname: Option, } #[derive(Debug, Insertable)] #[table_name = "release_contrib"] pub struct ReleaseContribNewRow { pub release_rev: Uuid, pub creator_ident_id: Option, pub raw_name: Option, pub role: Option, pub raw_affiliation: Option, pub index_val: Option, pub extra_json: Option, pub given_name: Option, pub surname: Option, } #[derive(Debug, Queryable, Insertable, Associations, AsChangeset)] #[table_name = "release_ref"] pub struct ReleaseRefRow { pub release_rev: Uuid, pub index_val: i32, pub target_release_ident_id: Uuid, } #[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, /// base32-encoded unique identifier #[serde(rename = "target_release_id")] #[serde(skip_serializing_if = "Option::is_none")] pub target_release_id: Option, #[serde(rename = "extra")] #[serde(skip_serializing_if = "Option::is_none")] pub extra: Option, #[serde(rename = "key")] #[serde(skip_serializing_if = "Option::is_none")] pub key: Option, #[serde(rename = "year")] #[serde(skip_serializing_if = "Option::is_none")] pub year: Option, #[serde(rename = "container_name")] #[serde(skip_serializing_if = "Option::is_none")] pub container_name: Option, #[serde(rename = "title")] #[serde(skip_serializing_if = "Option::is_none")] pub title: Option, #[serde(rename = "locator")] #[serde(skip_serializing_if = "Option::is_none")] pub locator: Option, } 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 { pub file_rev: Uuid, pub target_release_ident_id: Uuid, } #[derive(Debug, Queryable, Insertable, Associations, AsChangeset)] #[table_name = "fileset_rev_release"] pub struct FilesetRevReleaseRow { pub fileset_rev: Uuid, pub target_release_ident_id: Uuid, } #[derive(Debug, Queryable, Insertable, Associations, AsChangeset)] #[table_name = "webcapture_rev_release"] pub struct WebcaptureRevReleaseRow { pub webcapture_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 submitted: Option, pub is_accepted: bool, pub description: Option, pub extra_json: Option, } impl EditgroupRow { /// Returns an Editgroup 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, changelog_index: Option, editor: Option) -> Editgroup { Editgroup { editgroup_id: Some(uuid2fcid(&self.id)), editor_id: Some(uuid2fcid(&self.editor_id)), editor: editor, changelog_index: changelog_index, submitted: self .submitted .map(|v| chrono::DateTime::from_utc(v, chrono::Utc)), description: self.description, extra: self.extra_json, annotations: None, edits: None, } } } #[derive(Debug, Queryable, Identifiable, Associations, AsChangeset)] #[table_name = "editgroup_annotation"] pub struct EditgroupAnnotationRow { pub id: Uuid, pub editgroup_id: Uuid, pub editor_id: Uuid, pub created: chrono::NaiveDateTime, pub comment_markdown: Option, pub extra_json: Option, } impl EditgroupAnnotationRow { pub fn into_model(self) -> EditgroupAnnotation { EditgroupAnnotation { annotation_id: Some(self.id.to_string()), editgroup_id: Some(uuid2fcid(&self.editgroup_id)), editor_id: Some(uuid2fcid(&self.editor_id)), editor: None, created: Some(chrono::DateTime::from_utc(self.created, chrono::Utc)), comment_markdown: self.comment_markdown, extra: self.extra_json, } } } #[derive(Debug, Clone, Queryable, Identifiable, Associations, AsChangeset)] #[table_name = "editor"] pub struct EditorRow { pub id: Uuid, pub username: String, pub is_superuser: bool, pub is_admin: bool, pub is_bot: bool, pub is_active: bool, pub registered: chrono::NaiveDateTime, pub auth_epoch: chrono::NaiveDateTime, pub wrangler_id: Option, } impl EditorRow { pub fn into_model(self) -> Editor { Editor { editor_id: Some(uuid2fcid(&self.id)), username: self.username, is_admin: Some(self.is_admin), is_bot: Some(self.is_bot), is_active: Some(self.is_active), } } } #[derive(Debug, Clone, Queryable, Associations, AsChangeset)] #[table_name = "auth_oidc"] pub struct AuthOidcRow { pub id: i64, pub created: chrono::NaiveDateTime, pub editor_id: Uuid, pub provider: String, pub oidc_iss: String, pub oidc_sub: String, } #[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), } } }