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 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, with limits) * - db_create (single) * - db_update (single) * - db_expand (single) * */ 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 row: 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(row) } 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 could *alwas* return 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) } /// Note: this *still* doesn't epand the 'edits', at least yet. fn db_expand(&mut self, conn: &DbConn, expand: ExpandFlags) -> Result<()> { if expand.editors { let editor_id = FatcatId::from_str( self.editor_id .as_ref() .expect("tried to expand bare Editor model"), )?; self.editor = Some(Editor::db_get(conn, editor_id)?.into_model()); } 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_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_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_expand(&mut self, conn: &DbConn, expand: ExpandFlags) -> Result<()> { if expand.editors { let editor_id = FatcatId::from_str( self.editor_id .as_ref() .expect("tried to expand bare Editor model"), )?; self.editor = Some(Editor::db_get(conn, editor_id)?.into_model()); } 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_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) } }