From 9bb1b10d10cd2b1863c9e5f136621a845b0cb3c1 Mon Sep 17 00:00:00 2001 From: Bryan Newbold Date: Fri, 11 Jan 2019 11:59:02 -0800 Subject: WIP on annotations and editgroup accessors --- rust/src/database_models.rs | 28 ++++- rust/src/editing.rs | 2 +- rust/src/editing_crud.rs | 301 ++++++++++++++++++++++++++++++++++++++++++++ rust/src/entity_crud.rs | 6 + rust/src/lib.rs | 2 +- 5 files changed, 335 insertions(+), 4 deletions(-) create mode 100644 rust/src/editing_crud.rs diff --git a/rust/src/database_models.rs b/rust/src/database_models.rs index 4e83afdb..ef6eb65d 100644 --- a/rust/src/database_models.rs +++ b/rust/src/database_models.rs @@ -4,7 +4,7 @@ use crate::database_schema::*; use crate::errors::*; use crate::identifiers::uuid2fcid; use chrono; -use fatcat_api_spec::models::{ChangelogEntry, Editgroup, Editor, EntityEdit}; +use fatcat_api_spec::models::{ChangelogEntry, Editgroup, EditgroupAnnotation, Editor, EntityEdit}; use serde_json; use uuid::Uuid; @@ -556,8 +556,8 @@ pub struct EditgroupRow { pub created: chrono::NaiveDateTime, pub submitted: Option, pub is_accepted: bool, - pub extra_json: Option, pub description: Option, + pub extra_json: Option, } impl EditgroupRow { @@ -578,6 +578,30 @@ impl EditgroupRow { } } +#[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)), + 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 { diff --git a/rust/src/editing.rs b/rust/src/editing.rs index 2feff837..c2061ae9 100644 --- a/rust/src/editing.rs +++ b/rust/src/editing.rs @@ -7,7 +7,7 @@ use crate::database_models::*; use crate::database_schema::*; use crate::entity_crud::EntityCrud; use crate::errors::{FatcatError, Result}; -use crate::identifiers::{check_username, FatcatId}; +use crate::identifiers::FatcatId; use crate::server::DbConn; use diesel; use diesel::prelude::*; diff --git a/rust/src/editing_crud.rs b/rust/src/editing_crud.rs new file mode 100644 index 00000000..c409e368 --- /dev/null +++ b/rust/src/editing_crud.rs @@ -0,0 +1,301 @@ +use crate::database_models::*; +use crate::database_schema::*; +use crate::entity_crud::ExpandFlags; +use crate::errors::*; +use crate::identifiers::{self, FatcatId}; +use crate::server::DbConn; +use diesel::prelude::*; +use diesel::{self, insert_into}; +use fatcat_api_spec::models::*; +use std::str::FromStr; +use uuid::Uuid; + +/* + * The object types with accessors defined here: + * + * - editor + * - editgroup + * - editgroup_annotation + * + * Generic verbs/actions look like: + * + * - db_get (single) + * - db_get_range (group; by timestamp) + * - db_create (single) + * - db_update (single) + * - db_expand (single) + * + * Annotations can be fetch with a join on either editgroup or editor, with same range parameters. + */ + +pub trait EditorCrud { + fn db_get(conn: &DbConn, editor_id: FatcatId) -> Result; + fn db_create(&self, conn: &DbConn) -> Result; + fn db_update_username(&self, conn: &DbConn, editor_id: FatcatId) -> Result; +} +impl EditorCrud for Editor { + fn db_get(conn: &DbConn, editor_id: FatcatId) -> Result { + let editor: EditorRow = editor::table.find(editor_id.to_uuid()).get_result(conn)?; + Ok(editor) + } + + fn db_create(&self, conn: &DbConn) -> Result { + identifiers::check_username(&self.username)?; + let is_admin = self.is_admin.unwrap_or(false); + let is_bot = self.is_bot.unwrap_or(false); + let ed: EditorRow = diesel::insert_into(editor::table) + .values(( + editor::username.eq(&self.username), + editor::is_admin.eq(is_admin), + editor::is_bot.eq(is_bot), + )) + .get_result(conn)?; + Ok(ed) + } + + fn db_update_username(&self, conn: &DbConn, editor_id: FatcatId) -> Result { + identifiers::check_username(&self.username)?; + diesel::update(editor::table.find(editor_id.to_uuid())) + .set(editor::username.eq(&self.username)) + .execute(conn)?; + let editor: EditorRow = editor::table.find(editor_id.to_uuid()).get_result(conn)?; + Ok(editor) + } +} + +pub trait EditgroupCrud { + fn db_get(conn: &DbConn, editgroup_id: FatcatId) -> Result; + fn db_expand(&mut self, conn: &DbConn, expand: ExpandFlags) -> Result<()>; + fn db_get_range_for_editor( + conn: &DbConn, + editor_id: FatcatId, + limit: u64, + since: Option<()>, + before: Option<()>, + ) -> Result>; + fn db_get_range_reviewable( + conn: &DbConn, + limit: u64, + since: Option<()>, + before: Option<()>, + ) -> Result>; + fn db_create(&self, conn: &DbConn, autoaccept: bool) -> Result; + fn db_update( + &self, + conn: &DbConn, + editgroup_id: FatcatId, + submit: Option, + ) -> Result; +} + +impl EditgroupCrud for Editgroup { + // XXX: this should probably *alwas* retun changelog status as well. If we do that, can we get rid + // of the is_accepted thing? no, still want it as denormalized speed-up in some queries/filters. + + /// This method does *not* expand the 'edits'; currently that's still done in the endpoint + /// handler, but it probably should be done in this trait with a db_expand() + fn db_get(conn: &DbConn, editgroup_id: FatcatId) -> Result { + let row: EditgroupRow = editgroup::table + .find(editgroup_id.to_uuid()) + .get_result(conn)?; + + // Note: for now this is really conservative and verifies the is_accepted flag every time + let count: i64 = changelog::table + .filter(changelog::editgroup_id.eq(editgroup_id.to_uuid())) + .count() + .get_result(conn)?; + ensure!( + (count > 0) == row.is_accepted, + "internal database consistency error on editgroup: {}", + editgroup_id + ); + Ok(row) + } + + fn db_expand(&mut self, conn: &DbConn, expand: ExpandFlags) -> Result<()> { + // XXX: impl + Ok(()) + } + + fn db_get_range_for_editor( + conn: &DbConn, + editor_id: FatcatId, + limit: u64, + since: Option<()>, + before: Option<()>, + ) -> Result> { + // TODO: since/before + let rows: Vec = match (since, before) { + _ => { + editgroup::table + .filter(editgroup::editor_id.eq(editor_id.to_uuid())) + // XXX: .filter(editgroup::created + .order_by(editgroup::created.desc()) + .limit(limit as i64) + .get_results(conn)? + } + }; + Ok(rows) + } + + fn db_get_range_reviewable( + conn: &DbConn, + limit: u64, + since: Option<()>, + before: Option<()>, + ) -> Result> { + // TODO: since/before + let rows: Vec = match (since, before) { + _ => editgroup::table + .filter(editgroup::is_accepted.eq(false)) + .filter(editgroup::submitted.is_not_null()) + .order_by(editgroup::created.desc()) + .limit(limit as i64) + .get_results(conn)?, + }; + Ok(rows) + } + + fn db_create(&self, conn: &DbConn, autoaccept: bool) -> Result { + let editor_id = self + .editor_id + .clone() + .ok_or_else(|| FatcatError::BadRequest("missing editor_id".to_string()))?; + let editor_id = FatcatId::from_str(&editor_id)?; + let eg_row: EditgroupRow = diesel::insert_into(editgroup::table) + .values(( + editgroup::editor_id.eq(editor_id.to_uuid()), + editgroup::is_accepted.eq(autoaccept), + editgroup::description.eq(&self.description), + editgroup::extra_json.eq(&self.extra), + )) + .get_result(conn)?; + Ok(eg_row) + } + + fn db_update( + &self, + conn: &DbConn, + editgroup_id: FatcatId, + submit: Option, + ) -> Result { + let row = Self::db_get(conn, editgroup_id)?; + if row.is_accepted { + // "can't update an accepted editgroup" + Err(FatcatError::EditgroupAlreadyAccepted( + editgroup_id.to_string(), + ))?; + } + match submit { + Some(true) => { + // just a submit + let row = diesel::update(editgroup::table.find(editgroup_id.to_uuid())) + .set(editgroup::submitted.eq(diesel::dsl::now)) + .get_result(conn)?; + Ok(row) + } + Some(false) => { + // just a retraction + let submitted: Option = None; + let row = diesel::update(editgroup::table.find(editgroup_id.to_uuid())) + .set(editgroup::submitted.eq(submitted)) + .get_result(conn)?; + Ok(row) + } + None => { + // full-on row update... though we only do extra and description + let row = diesel::update(editgroup::table.find(editgroup_id.to_uuid())) + .set(( + editgroup::description.eq(&self.description), + editgroup::extra_json.eq(&self.extra), + )) + .get_result(conn)?; + Ok(row) + } + } + } +} + +pub trait EditgroupAnnotationCrud { + fn db_get(conn: &DbConn, annotation_id: Uuid) -> Result; + fn db_get_range_for_editor( + conn: &DbConn, + editor_id: FatcatId, + limit: u64, + since: Option<()>, + before: Option<()>, + ) -> Result>; + fn db_get_range_for_editgroup( + conn: &DbConn, + editgroup_id: FatcatId, + limit: u64, + since: Option<()>, + before: Option<()>, + ) -> Result>; + fn db_create(&self, conn: &DbConn) -> Result; +} + +impl EditgroupAnnotationCrud for EditgroupAnnotation { + fn db_get(conn: &DbConn, annotation_id: Uuid) -> Result { + let row: EditgroupAnnotationRow = editgroup_annotation::table + .find(annotation_id) + .get_result(conn)?; + Ok(row) + } + + fn db_get_range_for_editor( + conn: &DbConn, + editor_id: FatcatId, + limit: u64, + since: Option<()>, + before: Option<()>, + ) -> Result> { + // TODO: since/before + let rows: Vec = match (since, before) { + _ => editgroup_annotation::table + .filter(editgroup_annotation::editor_id.eq(editor_id.to_uuid())) + .order_by(editgroup_annotation::created.desc()) + .limit(limit as i64) + .get_results(conn)?, + }; + Ok(rows) + } + + fn db_get_range_for_editgroup( + conn: &DbConn, + editgroup_id: FatcatId, + limit: u64, + since: Option<()>, + before: Option<()>, + ) -> Result> { + // TODO: since/before + let rows: Vec = match (since, before) { + _ => editgroup_annotation::table + .filter(editgroup_annotation::editgroup_id.eq(editgroup_id.to_uuid())) + .order_by(editgroup_annotation::created.desc()) + .limit(limit as i64) + .get_results(conn)?, + }; + Ok(rows) + } + + fn db_create(&self, conn: &DbConn) -> Result { + let editor_id = self + .editor_id + .clone() + .ok_or_else(|| FatcatError::BadRequest("missing editor_id".to_string()))?; + let editor_id = FatcatId::from_str(&editor_id)?; + let editgroup_id = self + .editgroup_id + .clone() + .ok_or_else(|| FatcatError::BadRequest("missing editgroup_id".to_string()))?; + let editgroup_id = FatcatId::from_str(&editgroup_id)?; + let ed: EditgroupAnnotationRow = diesel::insert_into(editgroup_annotation::table) + .values(( + editgroup_annotation::editor_id.eq(editor_id.to_uuid()), + editgroup_annotation::editgroup_id.eq(editgroup_id.to_uuid()), + )) + .get_result(conn)?; + Ok(ed) + } +} diff --git a/rust/src/entity_crud.rs b/rust/src/entity_crud.rs index 3d9b5165..e54973da 100644 --- a/rust/src/entity_crud.rs +++ b/rust/src/entity_crud.rs @@ -104,6 +104,7 @@ pub struct ExpandFlags { pub container: bool, pub releases: bool, pub creators: bool, + pub editors: bool, } impl FromStr for ExpandFlags { @@ -123,6 +124,7 @@ impl ExpandFlags { container: list.contains(&"container"), releases: list.contains(&"releases"), creators: list.contains(&"creators"), + editors: list.contains(&"editors"), } } pub fn none() -> ExpandFlags { @@ -133,6 +135,7 @@ impl ExpandFlags { container: false, releases: false, creators: false, + editors: false, } } } @@ -150,6 +153,7 @@ fn test_expand_flags() { "other_thing", "releases", "creators", + "editors", ]); assert!( all == ExpandFlags { @@ -159,6 +163,7 @@ fn test_expand_flags() { container: true, releases: true, creators: true + editors: true } ); assert!(ExpandFlags::from_str("").unwrap().files == false); @@ -176,6 +181,7 @@ fn test_expand_flags() { container: true, releases: true, creators: true + editors: true } ); } diff --git a/rust/src/lib.rs b/rust/src/lib.rs index b4e5d17a..b7661334 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -14,7 +14,7 @@ pub mod auth; pub mod database_models; pub mod database_schema; // only public for tests pub mod editing; -//pub mod editing_crud; +pub mod editing_crud; mod endpoint_handlers; mod endpoints; pub mod entity_crud; -- cgit v1.2.3