diff --git a/cio/src/analytics.rs b/cio/src/analytics.rs index 5f87a3cf8..c66091549 100644 --- a/cio/src/analytics.rs +++ b/cio/src/analytics.rs @@ -18,8 +18,6 @@ use crate::{ #[db { new_struct_name = "PageView", - airtable_base = "customer_leads", - airtable_table = "AIRTABLE_PAGE_VIEWS_TABLE", match_on = { "time" = "DateTime", "user_email" = "String", @@ -42,29 +40,6 @@ pub struct NewPageView { pub cio_company_id: i32, } -/// Implement updating the Airtable record for a PageView. -#[async_trait] -impl UpdateAirtableRecord for PageView { - async fn update_airtable_record(&mut self, _record: PageView) -> Result<()> { - // Get the current auth users in Airtable so we can link to it. - // TODO: make this more dry so we do not call it every single damn time. - let db = Database::new().await; - let auth_users = AuthUsers::get_from_airtable(&db, self.cio_company_id).await?; - - // Iterate over the auth_users and see if we find a match. - for (_id, auth_user_record) in auth_users { - if auth_user_record.fields.email == self.user_email { - // Set the link_to_auth_user to the right user. - self.link_to_auth_user = vec![auth_user_record.id]; - // Break the loop and return early. - break; - } - } - - Ok(()) - } -} - impl NewPageView { pub fn set_page_link(&mut self) { // Set the link. @@ -84,12 +59,3 @@ impl NewPageView { Ok(()) } } - -pub async fn refresh_analytics(db: &Database, company: &Company) -> Result<()> { - PageViews::get_from_db(db, company.id) - .await? - .update_airtable(db) - .await?; - - Ok(()) -} diff --git a/cio/src/api_tokens.rs b/cio/src/api_tokens.rs index 321b1ad41..611bbc5fc 100644 --- a/cio/src/api_tokens.rs +++ b/cio/src/api_tokens.rs @@ -16,8 +16,6 @@ use crate::{ #[db { new_struct_name = "APIToken", - airtable_base = "cio", - airtable_table = "AIRTABLE_API_TOKENS_TABLE", match_on = { "auth_company_id" = "i32", "product" = "String", @@ -69,19 +67,6 @@ pub struct NewAPIToken { pub auth_company_id: i32, } -/// Implement updating the Airtable record for a APIToken. -#[async_trait] -impl UpdateAirtableRecord for APIToken { - async fn update_airtable_record(&mut self, _record: APIToken) -> Result<()> { - // Link to the correct company. - let db = Database::new().await; - let company = Company::get_by_id(&db, self.auth_company_id).await?; - self.company = vec![company.airtable_record_id]; - - Ok(()) - } -} - impl NewAPIToken { pub fn expand(&mut self) { if self.expires_in > 0 { @@ -136,12 +121,3 @@ impl APIToken { // } } } - -pub async fn refresh_api_tokens(db: &Database, company: &Company) -> Result<()> { - APITokens::get_from_db(db, company.id) - .await? - .update_airtable(db) - .await?; - - Ok(()) -} diff --git a/cio/src/applicant_reviews.rs b/cio/src/applicant_reviews.rs index 7e86581db..9c02d6d3e 100644 --- a/cio/src/applicant_reviews.rs +++ b/cio/src/applicant_reviews.rs @@ -17,8 +17,6 @@ use crate::{ #[db { new_struct_name = "ApplicantReview", - airtable_base = "hiring", - airtable_table = "AIRTABLE_REVIEWS_TABLE", match_on = { "name" = "String", "applicant" = "Vec", @@ -105,17 +103,6 @@ pub struct NewApplicantReview { pub cio_company_id: i32, } -/// Implement updating the Airtable record for a ApplicantReview. -#[async_trait] -impl UpdateAirtableRecord for ApplicantReview { - async fn update_airtable_record(&mut self, _record: ApplicantReview) -> Result<()> { - // Set name to empty since it is a function we cannot update it. - self.name = "".to_string(); - - Ok(()) - } -} - impl ApplicantReview { pub async fn expand(&mut self, db: &Database) -> Result<()> { let company = self.company(db).await?; @@ -154,60 +141,3 @@ impl ApplicantReview { Ok(()) } } - -pub async fn refresh_reviews(db: &Database, company: &Company) -> Result<()> { - if company.airtable_base_id_hiring.is_empty() { - // Return early. - return Ok(()); - } - - let company_id = company.id; - - let client = company.authenticate_airtable(&company.airtable_base_id_hiring); - let mut pages = client.pages::(&ApplicantReview::airtable_table(), "Grid view", vec![]); - - while let Some(records) = pages.next().await? { - let records = records - .into_iter() - .filter(|record| !record.fields.name.is_empty() && !record.fields.applicant.is_empty()) - .collect::>(); - - let chunks: Vec> = records.chunks(10).map(|chunk| chunk.to_vec()).collect(); - - for chunk in chunks { - let mut tasks = vec![]; - - for record in chunk { - let db = db.clone(); - - tasks.push(tokio::spawn(async move { - let new_review: NewApplicantReview = record.fields.into(); - - let mut review = new_review.upsert_in_db(&db).await?; - if review.airtable_record_id.is_empty() { - review.airtable_record_id = record.id; - } - review.cio_company_id = company_id; - - review.expand(&db).await?; - review.update(&db).await?; - - Ok::<(), anyhow::Error>(()) - })); - } - - futures::future::join_all(tasks) - .await - .into_iter() - .collect::, tokio::task::JoinError>>()?; - } - } - - // Update them all from the database. - // ApplicantReviews::get_from_db(db, company.id) - // .await? - // .update_airtable(db) - // .await?; - - Ok(()) -} diff --git a/cio/src/applicants.rs b/cio/src/applicants.rs index b10fdcca7..43d73fa58 100644 --- a/cio/src/applicants.rs +++ b/cio/src/applicants.rs @@ -64,8 +64,6 @@ impl From<&OnboardingAssignees> for Vec { /// The data type for a NewApplicant. #[db { new_struct_name = "Applicant", - airtable_base = "hiring", - airtable_table = "AIRTABLE_APPLICATIONS_TABLE", match_on = { "email" = "String", "sheet_id" = "String", @@ -461,7 +459,7 @@ impl Applicant { // Initialize the GSuite sheets client. let drive_client = company.authenticate_google_drive(db).await?; - self.keep_fields_from_airtable(db).await; + // self.keep_fields_from_airtable(db).await; // Expand the application. if let Err(e) = self.expand(db, &drive_client, &app_config.apply).await { @@ -565,7 +563,7 @@ impl Applicant { /// Update applicant reviews counts. pub async fn update_reviews_scoring(&mut self, db: &Database) -> Result<()> { - self.keep_fields_from_airtable(db).await; + // self.keep_fields_from_airtable(db).await; // Create the Airtable client. let company = Company::get_by_id(db, self.cio_company_id).await?; @@ -750,7 +748,7 @@ impl Applicant { /// Send an invite to the applicant to do a background check. pub async fn send_background_check_invitation(&mut self, db: &Database) -> Result<()> { // Keep the fields from Airtable we need just in case they changed. - self.keep_fields_from_airtable(db).await; + // self.keep_fields_from_airtable(db).await; let company = self.company(db).await?; let checkr_auth = company.authenticate_checkr(); @@ -1170,21 +1168,6 @@ fn parse_question(q1: &str, q2: &str, materials_contents: &str) -> String { Default::default() } -/// Implement updating the Airtable record for an Applicant. -#[async_trait] -impl UpdateAirtableRecord for Applicant { - async fn update_airtable_record(&mut self, record: Applicant) -> Result<()> { - self.interviews = record.interviews; - self.geocode_cache = record.geocode_cache; - self.link_to_reviews = record.link_to_reviews; - self.resume_contents = truncate(&self.resume_contents, 90000); - self.materials_contents = truncate(&self.materials_contents, 90000); - self.question_why_oxide = truncate(&self.question_why_oxide, 90000); - - Ok(()) - } -} - /// Get the contexts of a file in Google Drive by it's URL as a text string. pub async fn get_file_contents(drive_client: &GoogleDrive, url: &str) -> Result { let id = url @@ -1291,8 +1274,6 @@ async fn read_pdf(name: &str, path: std::path::PathBuf) -> Result { /// The data type for a ApplicantReviewer. #[db { new_struct_name = "ApplicantReviewer", - airtable_base = "hiring", - airtable_table = "AIRTABLE_REVIEWER_LEADERBOARD_TABLE", match_on = { "email" = "String", }, @@ -1326,14 +1307,6 @@ pub struct NewApplicantReviewer { pub cio_company_id: i32, } -/// Implement updating the Airtable record for an ApplicantReviewer. -#[async_trait] -impl UpdateAirtableRecord for ApplicantReviewer { - async fn update_airtable_record(&mut self, _record: ApplicantReviewer) -> Result<()> { - Ok(()) - } -} - pub async fn refresh_docusign_for_applicants(db: &Database, company: &Company, config: &AppConfig) -> Result<()> { if company.airtable_base_id_hiring.is_empty() { // Return early. @@ -1977,7 +1950,7 @@ The applicants Airtable \ new_envelope: docusign::Envelope, ) -> Result<()> { // Keep the fields from Airtable we need just in case they changed. - self.keep_fields_from_airtable(db).await; + // self.keep_fields_from_airtable(db).await; // We look for "Onboarding" here as well since we want to make sure we can actually update // the data for the user. @@ -2026,7 +1999,7 @@ The applicants Airtable \ envelope: docusign::Envelope, ) -> Result<()> { // Keep the fields from Airtable we need just in case they changed. - self.keep_fields_from_airtable(db).await; + // self.keep_fields_from_airtable(db).await; let company = self.company(db).await?; @@ -2182,7 +2155,7 @@ The applicants Airtable \ new_envelope: docusign::Envelope, ) -> Result<()> { // Keep the fields from Airtable we need just in case they changed. - self.keep_fields_from_airtable(db).await; + // self.keep_fields_from_airtable(db).await; // We look for "Onboarding" here as well since we want to make sure we can actually update // the data for the user. @@ -2231,7 +2204,7 @@ The applicants Airtable \ new_envelope: docusign::Envelope, ) -> Result<()> { // Keep the fields from Airtable we need just in case they changed. - self.keep_fields_from_airtable(db).await; + // self.keep_fields_from_airtable(db).await; // Only allow documents to be re-generated if we are in the process of or have already // hired this applicant @@ -2270,35 +2243,35 @@ The applicants Airtable \ } } - pub async fn keep_fields_from_airtable(&mut self, db: &Database) { - // Let's get the existing record from Airtable, so we can use it as the source - // of truth for various things. - if let Some(ex) = self.get_existing_airtable_record(db).await { - let existing = ex.fields; - // We keep the scorers from Airtable in case someone assigned someone from the UI. - self.scorers = existing.scorers.clone(); - // Keep the interviewers from Airtable since they are updated out of bound by Airtable. - self.interviews = existing.interviews.clone(); - // Keep the reviews, since these are updated out of band by Airtable. - self.link_to_reviews = existing.link_to_reviews; - - // We want to keep the status and status raw since we might have modified - // it to move a candidate along in the process. - self.status = existing.status.to_string(); - self.raw_status = existing.raw_status.to_string(); - - // Mostly the start date will populate from docusign, but just in case they - // are someone who worked remotely, we might have to manually set it. - // If docusign is incorrect, make sure Airtable always has the source of truth. - self.start_date = existing.start_date; - } else { - log::warn!( - "Could not find existing Airtable record for email -> {}, id -> {}", - self.email, - self.id - ); - } - } + // pub async fn keep_fields_from_airtable(&mut self, db: &Database) { + // // Let's get the existing record from Airtable, so we can use it as the source + // // of truth for various things. + // if let Some(ex) = self.get_existing_airtable_record(db).await { + // let existing = ex.fields; + // // We keep the scorers from Airtable in case someone assigned someone from the UI. + // self.scorers = existing.scorers.clone(); + // // Keep the interviewers from Airtable since they are updated out of bound by Airtable. + // self.interviews = existing.interviews.clone(); + // // Keep the reviews, since these are updated out of band by Airtable. + // self.link_to_reviews = existing.link_to_reviews; + + // // We want to keep the status and status raw since we might have modified + // // it to move a candidate along in the process. + // self.status = existing.status.to_string(); + // self.raw_status = existing.raw_status.to_string(); + + // // Mostly the start date will populate from docusign, but just in case they + // // are someone who worked remotely, we might have to manually set it. + // // If docusign is incorrect, make sure Airtable always has the source of truth. + // self.start_date = existing.start_date; + // } else { + // log::warn!( + // "Could not find existing Airtable record for email -> {}, id -> {}", + // self.email, + // self.id + // ); + // } + // } pub async fn update_applicant_from_docusign_piia_envelope( &mut self, @@ -2307,7 +2280,7 @@ The applicants Airtable \ envelope: docusign::Envelope, ) -> Result<()> { // Keep the fields from Airtable we need just in case they changed. - self.keep_fields_from_airtable(db).await; + // self.keep_fields_from_airtable(db).await; let company = self.company(db).await?; @@ -2481,11 +2454,6 @@ pub async fn refresh_new_applicants_and_reviews( } } - // Update Airtable. - // TODO: this might cause some racy problems, maybe only run at night (?) - // Or maybe always get the latest from the database and update airtable with that (?) - // Applicants::get_from_db(db, company.id)?.update_airtable(db).await?; - Ok(()) } diff --git a/cio/src/asset_inventory.rs b/cio/src/asset_inventory.rs index 3dbbd8a56..386ac8000 100644 --- a/cio/src/asset_inventory.rs +++ b/cio/src/asset_inventory.rs @@ -22,8 +22,6 @@ use crate::{ #[db { new_struct_name = "AssetItem", - airtable_base = "assets", - airtable_table = "AIRTABLE_ASSET_ITEMS_TABLE", match_on = { "cio_company_id" = "i32", "name" = "String", @@ -97,14 +95,6 @@ pub struct NewAssetItem { pub cio_company_id: i32, } -/// Implement updating the Airtable record for a AssetItem. -#[async_trait] -impl UpdateAirtableRecord for AssetItem { - async fn update_airtable_record(&mut self, _record: AssetItem) -> Result<()> { - Ok(()) - } -} - impl NewAssetItem { pub fn generate_barcode(&self) -> String { let mut barcode = self @@ -283,70 +273,3 @@ impl AssetItem { Ok(()) } } - -/// Sync asset items from Airtable. -pub async fn refresh_asset_items(db: &Database, company: &Company) -> Result<()> { - if company.airtable_base_id_assets.is_empty() { - // Return early. - return Ok(()); - } - - // Initialize the Google Drive client. - let mut drive_client = company.authenticate_google_drive(db).await?; - - // Figure out where our directory is. - // It should be in the shared drive : "Automated Documents"/"rfds" - let shared_drive = drive_client.drives().get_by_name("Automated Documents").await?.body; - let drive_id = shared_drive.id.to_string(); - - // Get the directory by the name. - let parent_id = drive_client - .files() - .create_folder(&drive_id, "", "assets") - .await? - .body - .id; - - // Get all the records from Airtable. - let results: Vec> = company - .authenticate_airtable(&company.airtable_base_id_assets) - .list_records(&AssetItem::airtable_table(), "Grid view", vec![]) - .await?; - - for item_record in results { - let mut item: NewAssetItem = item_record.fields.into(); - if item.name.is_empty() { - // A new generator is created for each use as the Generator is not Send - item.name = names::Generator::default().next().unwrap(); - } - - // Iterating through and processing all of the asset items can take over an hour. This - // exceeds the time limit that Google Drive allots for a single token. Therefore we may - // need to refresh the access token mid processing if an item expansion fails - match item.expand(&drive_client, &drive_id, &parent_id).await { - Ok(_) => (), - Err(err) => { - log::info!("Handling drive error. This is likely to be an authentication error. Further work is needed to differentiate. {:?}", err); - log::info!("Reauthenticating with Google Drive"); - drive_client = company.authenticate_google_drive(db).await?; - - // Now using a client with fresh credentials, we can retry the expansion. If this - // again, it is unlikely due to an authentication error - item.expand(&drive_client, &drive_id, &parent_id).await?; - } - } - - item.cio_company_id = company.id; - - let mut db_item = item.upsert_in_db(db).await?; - db_item.airtable_record_id = item_record.id.to_string(); - db_item.update(db).await?; - } - - AssetItems::get_from_db(db, company.id) - .await? - .update_airtable(db) - .await?; - - Ok(()) -} diff --git a/cio/src/auth_logins.rs b/cio/src/auth_logins.rs index 2a5d5b92e..6513b3038 100644 --- a/cio/src/auth_logins.rs +++ b/cio/src/auth_logins.rs @@ -18,8 +18,6 @@ use crate::{ /// The data type for an NewAuthUser. #[db { new_struct_name = "AuthUser", - airtable_base = "customer_leads", - airtable_table = "AIRTABLE_AUTH_USERS_TABLE", custom_partial_eq = true, match_on = { "user_id" = "String", @@ -79,19 +77,6 @@ pub struct NewAuthUser { pub cio_company_id: i32, } -/// Implement updating the Airtable record for a AuthUser. -#[async_trait] -impl UpdateAirtableRecord for AuthUser { - async fn update_airtable_record(&mut self, record: AuthUser) -> Result<()> { - // Set the link_to_people and link_to_auth_user_logins from the original so it stays intact. - self.link_to_people = record.link_to_people.clone(); - self.link_to_auth_user_logins = record.link_to_auth_user_logins; - self.link_to_page_views = record.link_to_page_views; - - Ok(()) - } -} - impl PartialEq for AuthUser { // We implement our own here because Airtable has a different data type for the picture. fn eq(&self, other: &Self) -> bool { @@ -106,8 +91,6 @@ impl PartialEq for AuthUser { /// The data type for a NewAuthUserLogin. #[db { new_struct_name = "AuthUserLogin", - airtable_base = "customer_leads", - airtable_table = "AIRTABLE_AUTH_USER_LOGINS_TABLE", match_on = { "user_id" = "String", "date" = "DateTime", @@ -160,26 +143,3 @@ pub struct NewAuthUserLogin { #[serde(default)] pub cio_company_id: i32, } - -/// Implement updating the Airtable record for a AuthUserLogin. -#[async_trait] -impl UpdateAirtableRecord for AuthUserLogin { - async fn update_airtable_record(&mut self, _record: AuthUserLogin) -> Result<()> { - // Get the current auth users in Airtable so we can link to it. - // TODO: make this more dry so we do not call it every single damn time. - let db = Database::new().await; - let auth_users = AuthUsers::get_from_airtable(&db, self.cio_company_id).await?; - - // Iterate over the auth_users and see if we find a match. - for (_id, auth_user_record) in auth_users { - if auth_user_record.fields.user_id == self.user_id { - // Set the link_to_auth_user to the right user. - self.link_to_auth_user = vec![auth_user_record.id]; - // Break the loop and return early. - break; - } - } - - Ok(()) - } -} diff --git a/cio/src/certs.rs b/cio/src/certs.rs index 47b2dc350..326b7af05 100644 --- a/cio/src/certs.rs +++ b/cio/src/certs.rs @@ -40,8 +40,6 @@ use crate::{ /// A data type to hold the values of a let's encrypt certificate for a domain. #[db { new_struct_name = "Certificate", - airtable_base = "misc", - airtable_table = "AIRTABLE_CERTIFICATES_TABLE", match_on = { "cio_company_id" = "i32", "domain" = "String", @@ -286,14 +284,6 @@ impl NewCertificate { } } -/// Implement updating the Airtable record for a Certificate. -#[async_trait] -impl UpdateAirtableRecord for Certificate { - async fn update_airtable_record(&mut self, _record: Certificate) -> Result<()> { - Ok(()) - } -} - impl NewCertificate { pub async fn renew<'a>( &'a mut self, diff --git a/cio/src/companies.rs b/cio/src/companies.rs index c9d3f6229..9044b904d 100644 --- a/cio/src/companies.rs +++ b/cio/src/companies.rs @@ -64,8 +64,6 @@ use crate::{ #[db { new_struct_name = "Company", - airtable_base = "cio", - airtable_table = "AIRTABLE_COMPANIES_TABLE", match_on = { "name" = "String", }, @@ -166,14 +164,6 @@ pub struct NewCompany { pub cio_company_id: i32, } -/// Implement updating the Airtable record for a Company. -#[async_trait] -impl UpdateAirtableRecord for Company { - async fn update_airtable_record(&mut self, _record: Company) -> Result<()> { - Ok(()) - } -} - impl Company { /// Returns the shippo data structure for the address at the office /// for the company. @@ -1154,36 +1144,6 @@ pub struct RFDRepo { pub default_branch: String, } -pub async fn refresh_companies(db: &Database) -> Result<()> { - // This should forever only be Oxide. - let oxide = Company::get_from_db(db, "Oxide".to_string()).await.unwrap(); - - let is: Vec> = oxide - .authenticate_airtable(&oxide.airtable_base_id_cio) - .list_records(&Company::airtable_table(), AIRTABLE_GRID_VIEW, vec![]) - .await?; - - for record in is { - if record.fields.name.is_empty() || record.fields.website.is_empty() { - // Ignore it, it's a blank record. - continue; - } - - let new_company: NewCompany = record.fields.into(); - - let mut company = new_company.upsert_in_db(db).await?; - if company.airtable_record_id.is_empty() { - company.airtable_record_id = record.id; - } - company.cio_company_id = oxide.id; - company.update(db).await?; - } - // Companies are only stored with Oxide. - Companys::get_from_db(db, 1).await?.update_airtable(db).await?; - - Ok(()) -} - pub fn get_google_scopes() -> Vec { vec![ "https://www.googleapis.com/auth/admin.directory.group".to_string(), diff --git a/cio/src/configs.rs b/cio/src/configs.rs index a61c9dade..b41f4c5e7 100644 --- a/cio/src/configs.rs +++ b/cio/src/configs.rs @@ -159,8 +159,6 @@ impl FromSql for ExternalServices { /// The data type for a user. #[db { new_struct_name = "User", - airtable_base = "directory", - airtable_table = "AIRTABLE_EMPLOYEES_TABLE", match_on = { "cio_company_id" = "i32", "username" = "String", @@ -445,63 +443,13 @@ impl UserConfig { } } - // If we have an existing user, sync down Airtable fields that we allow modifications on - if let Some(e) = &existing { - if let Some(airtable_record) = e.get_existing_airtable_record(db).await { - self.home_address_street_1 = airtable_record.fields.home_address_street_1.to_string(); - self.home_address_street_2 = airtable_record.fields.home_address_street_2.to_string(); - self.home_address_city = airtable_record.fields.home_address_city.to_string(); - self.home_address_state = airtable_record.fields.home_address_state.to_string(); - self.home_address_zipcode = airtable_record.fields.home_address_zipcode.to_string(); - self.home_address_country = airtable_record.fields.home_address_country.to_string(); - self.birthday = airtable_record.fields.birthday; - - log::info!( - "Fetched address data from existing Airtable record for user {} during sync", - e.id - ); - } else { - log::info!("Failed to find existing Airtable record for user {} during sync", e.id); - } - } - // See if we have a gusto user for the user. // The user's email can either be their personal email or their oxide email. if let Some(gusto_user) = gusto_users.get(&self.email) { self.update_from_gusto(gusto_user); } else if let Some(gusto_user) = gusto_users.get(&self.recovery_email) { self.update_from_gusto(gusto_user); - } else { - // For a new hire we may have an airtable entry, but not a Gusto record. Grab their - // date of birth, start date, and address from Airtable. - if let Some(e) = &existing { - // Redundant lookup - if let Some(airtable_record) = e.get_existing_airtable_record(db).await { - // Keep the start date in airtable if we already have one. - if self.start_date == crate::utils::default_date() - && airtable_record.fields.start_date != crate::utils::default_date() - { - self.start_date = airtable_record.fields.start_date; - } - - self.gusto_id = airtable_record.fields.gusto_id; - } - - // If we found a Gusto id in Airtable then update the user record based on that id. - // TODO: This logic (combined with the email lookup above is very likely incorrect. - // It is possible (though unlikely) that the two of these diverge and result in - // returning different accounts) - if !e.gusto_id.is_empty() { - if let Some(gusto_user) = gusto_users_by_id.get(&e.gusto_id) { - self.update_from_gusto(gusto_user); - } - } else if let Ok((ref gusto, ref gusto_company_id)) = gusto_auth { - self.populate_home_address().await?; - // Create the user in Gusto if necessary. - self.create_in_gusto_if_needed(gusto, gusto_company_id).await?; - } - } - } + } else { } // Expand the user. self.expand(db, company).await?; @@ -975,7 +923,7 @@ impl User { // Let's create the shipment. let new_shipment = NewOutboundShipment::from(self.clone()); // Let's add it to our database. - let mut shipment = new_shipment.upsert_in_db(db).await?; + let mut shipment = new_shipment.upsert(db).await?; // Create the shipment in shippo. shipment.create_or_get_shippo_shipment(db).await?; // Update airtable and the database again. @@ -1242,67 +1190,9 @@ xoxo, } } -/// Implement updating the Airtable record for a User. -#[async_trait] -impl UpdateAirtableRecord for User { - async fn update_airtable_record(&mut self, record: User) -> Result<()> { - // Get the current groups in Airtable so we can link to them. - // TODO: make this more dry so we do not call it every single damn time. - let db = Database::new().await; - let groups = Groups::get_from_airtable(&db, self.cio_company_id).await?; - - let mut links: Vec = Default::default(); - // Iterate over the group names in our record and match it against the - // group ids and see if we find a match. - for group in &self.groups { - // Iterate over the groups to get the ID. - for g in groups.values() { - if *group == g.fields.name { - // Append the ID to our links. - links.push(g.id.to_string()); - // Break the loop and return early. - break; - } - } - } - - self.groups = links; - - self.geocode_cache = record.geocode_cache.to_string(); - - if self.start_date == crate::utils::default_date() && record.start_date != crate::utils::default_date() { - self.start_date = record.start_date; - } - - if !record.google_anniversary_event_id.is_empty() { - self.google_anniversary_event_id = record.google_anniversary_event_id.to_string(); - } - - // Set the building to right building link. - // Get the current buildings in Airtable so we can link to it. - // TODO: make this more dry so we do not call it every single damn time. - let buildings = Buildings::get_from_airtable(&db, self.cio_company_id).await?; - // Iterate over the buildings to get the ID. - for building in buildings.values() { - if self.building == building.fields.name { - // Set the ID. - self.link_to_building = vec![building.id.to_string()]; - // Break the loop and return early. - break; - } - } - - self.work_address_formatted = self.work_address_formatted.replace("\\n", "\n"); - - Ok(()) - } -} - /// The data type for a group. This applies to Google Groups. #[db { new_struct_name = "Group", - airtable_base = "directory", - airtable_table = "AIRTABLE_GROUPS_TABLE", match_on = { "cio_company_id" = "i32", "name" = "String", @@ -1474,21 +1364,9 @@ impl Group { } } -/// Implement updating the Airtable record for a Group. -#[async_trait] -impl UpdateAirtableRecord for Group { - async fn update_airtable_record(&mut self, record: Group) -> Result<()> { - // Make sure we don't mess with the members since that is populated by the Users table. - self.members = record.members; - Ok(()) - } -} - /// The data type for a building. #[db { new_struct_name = "Building", - airtable_base = "directory", - airtable_table = "AIRTABLE_BUILDINGS_TABLE", match_on = { "cio_company_id" = "i32", "name" = "String", @@ -1541,21 +1419,6 @@ impl BuildingConfig { } } -/// Implement updating the Airtable record for a Building. -#[async_trait] -impl UpdateAirtableRecord for Building { - async fn update_airtable_record(&mut self, record: Building) -> Result<()> { - // Make sure we don't mess with the employees since that is populated by the Users table. - self.employees = record.employees.clone(); - // Make sure we don't mess with the conference_rooms since that is populated by the Conference Rooms table. - self.conference_rooms = record.conference_rooms; - - self.geocode_cache = record.geocode_cache; - - Ok(()) - } -} - #[derive(Debug, Clone, PartialEq, JsonSchema, Serialize, Deserialize, FromSqlRow, AsExpression, Default)] #[diesel(sql_type = VarChar)] pub enum ResourceCategory { @@ -1604,8 +1467,6 @@ fn default_resource_category() -> ResourceCategory { /// availability that people can book through GSuite. #[db { new_struct_name = "Resource", - airtable_base = "directory", - airtable_table = "AIRTABLE_RESOURCES_TABLE", match_on = { "cio_company_id" = "i32", "name" = "String", @@ -1635,35 +1496,10 @@ pub struct NewResourceConfig { pub cio_company_id: i32, } -/// Implement updating the Airtable record for a Resource. -#[async_trait] -impl UpdateAirtableRecord for Resource { - async fn update_airtable_record(&mut self, _record: Resource) -> Result<()> { - // Set the building to right building link. - // Get the current buildings in Airtable so we can link to it. - // TODO: make this more dry so we do not call it every single damn time. - let db = Database::new().await; - let buildings = Buildings::get_from_airtable(&db, self.cio_company_id).await?; - // Iterate over the buildings to get the ID. - for building in buildings.values() { - if self.building == building.fields.name { - // Set the ID. - self.link_to_building = vec![building.id.to_string()]; - // Break the loop and return early. - break; - } - } - - Ok(()) - } -} - /// The data type for a link. These get turned into short links like /// `{name}.corp.oxide.compuer` by the `shorturls` subcommand. #[db { new_struct_name = "Link", - airtable_base = "directory", - airtable_table = "AIRTABLE_LINKS_TABLE", match_on = { "cio_company_id" = "i32", "name" = "String", @@ -1686,14 +1522,6 @@ pub struct LinkConfig { pub cio_company_id: i32, } -/// Implement updating the Airtable record for a Link. -#[async_trait] -impl UpdateAirtableRecord for Link { - async fn update_airtable_record(&mut self, _record: Link) -> Result<()> { - Ok(()) - } -} - /// The data type for a huddle meeting that syncs with Airtable and notes in GitHub. #[derive(Debug, Default, PartialEq, Clone, JsonSchema, Deserialize, Serialize)] pub struct HuddleConfig { @@ -2123,9 +1951,6 @@ pub async fn sync_users( info!("updated configs users in the database"); - // Update users in airtable. - Users::get_from_db(db, company.id).await?.update_airtable(db).await?; - Ok(()) } @@ -2247,12 +2072,6 @@ pub async fn sync_buildings( info!("created building from gsuite: {}", id); } - // Update buildings in airtable. - Buildings::get_from_db(db, company.id) - .await? - .update_airtable(db) - .await?; - Ok(()) } @@ -2365,12 +2184,6 @@ pub async fn sync_resources( info!("created conference room in gsuite: {}", id); } - // Update resources in airtable. - Resources::get_from_db(db, company.id) - .await? - .update_airtable(db) - .await?; - Ok(()) } @@ -2437,9 +2250,6 @@ pub async fn sync_groups(db: &Database, groups: BTreeMap, c // } } - // Update groups in airtable. - Groups::get_from_db(db, company.id).await?.update_airtable(db).await?; - Ok(()) } @@ -2507,9 +2317,6 @@ pub async fn sync_links( } info!("updated configs links in the database"); - // Update links in airtable. - Links::get_from_db(db, company.id).await?.update_airtable(db).await?; - Ok(()) } @@ -2576,12 +2383,6 @@ pub async fn sync_certificates( } info!("updated configs certificates in the database"); - // Update certificates in airtable. - Certificates::get_from_db(db, company.id) - .await? - .update_airtable(db) - .await?; - Ok(()) } diff --git a/cio/src/finance.rs b/cio/src/finance.rs index ede2799d3..24c2218b4 100644 --- a/cio/src/finance.rs +++ b/cio/src/finance.rs @@ -28,8 +28,6 @@ use crate::{ #[db { new_struct_name = "SoftwareVendor", - airtable_base = "finance", - airtable_table = "AIRTABLE_SOFTWARE_VENDORS_TABLE", match_on = { "cio_company_id" = "i32", "name" = "String", @@ -85,23 +83,6 @@ fn is_zero(num: &f32) -> bool { *num == 0.0 } -/// Implement updating the Airtable record for a SoftwareVendor. -#[async_trait] -impl UpdateAirtableRecord for SoftwareVendor { - async fn update_airtable_record(&mut self, record: SoftwareVendor) -> Result<()> { - // This is a function so we can't change it through the API. - self.total_cost_per_month = 0.0; - // Keep this the same, we update it from the transactions. - self.link_to_transactions = record.link_to_transactions; - // Keep this the same, we update it from the accounts payable. - self.link_to_accounts_payable = record.link_to_accounts_payable; - // Keep this the same, we update it from the expensed items. - self.link_to_expensed_items = record.link_to_expensed_items; - - Ok(()) - } -} - /// Convert the vendor into a Slack message. impl From for FormattedMessage { fn from(item: NewSoftwareVendor) -> Self { @@ -222,111 +203,8 @@ impl NewSoftwareVendor { } } -/// Sync software vendors from Airtable. -pub async fn refresh_software_vendors(db: &Database, company: &Company) -> Result<()> { - let gsuite = company.authenticate_google_admin(db).await?; - - let github = company.authenticate_github()?; - - let okta_auth = company.authenticate_okta(); - - let slack_auth = company.authenticate_slack(db).await; - - // Get all the records from Airtable. - let results: Vec> = company - .authenticate_airtable(&company.airtable_base_id_finance) - .list_records(&SoftwareVendor::airtable_table(), "Grid view", vec![]) - .await?; - for vendor_record in results { - let mut vendor: NewSoftwareVendor = vendor_record.fields.into(); - - let new_cost_per_user_per_month = vendor.cost_per_user_per_month; - - // Get the existing record if there is one. - let existing = SoftwareVendor::get_from_db(db, company.id, vendor.name.to_string()).await; - // Set the existing cost and number of users, since we want to know - // via slack notification if it changed. - vendor.cost_per_user_per_month = if let Some(ref ex) = existing { - ex.cost_per_user_per_month - } else { - 0.0 - }; - vendor.users = if let Some(ref ex) = existing { ex.users } else { 0 }; - - // Set the company id. - vendor.cio_company_id = company.id; - - let users = if vendor.name == "GitHub" { - // Update the number of GitHub users in our org. - let org = github.orgs().get(&company.github_org).await?.body; - org.plan.unwrap().filled_seats as i32 - } else if vendor.name == "Okta" && okta_auth.is_some() { - let okta = okta_auth.as_ref().unwrap(); - let users = okta.list_provider_users(company).await?; - users.len() as i32 - } else if vendor.name == "Google Workspace" { - let users = gsuite.list_provider_users(company).await?; - users.len() as i32 - } else if vendor.name == "Slack" && slack_auth.is_ok() { - let slack = slack_auth.as_ref().unwrap(); - let users = slack.billable_info().await?; - let mut count = 0; - for (_, user) in users { - if user.billing_active { - count += 1; - } - } - - count - } else if vendor.name == "Airtable" - || vendor.name == "Ramp" - || vendor.name == "Brex" - || vendor.name == "Gusto" - || vendor.name == "Expensify" - { - // Airtable, Brex, Gusto, Expensify are all the same number of users as - // in all@. - let group = Group::get_from_db(db, company.id, "all".to_string()).await.unwrap(); - let airtable_group = group.get_existing_airtable_record(db).await.unwrap(); - - airtable_group.fields.members.len() as i32 - } else { - vendor.users - }; - - // Send the slack notification if the number of users or cost changed. - // This will also set the values for users and cost_per_user_per_month, so - // do this before sending to the database. - vendor - .send_slack_notification_if_price_changed(db, company, users, new_cost_per_user_per_month) - .await?; - - // Upsert the record in our database. - let mut db_vendor = vendor.upsert_in_db(db).await?; - - if db_vendor.airtable_record_id.is_empty() { - db_vendor.airtable_record_id = vendor_record.id; - } - - // Update the cost per month. - db_vendor.total_cost_per_month = - (db_vendor.cost_per_user_per_month * db_vendor.users as f32) + db_vendor.flat_cost_per_month; - - db_vendor.update(db).await?; - } - - SoftwareVendors::get_from_db(db, company.id) - .await? - .update_airtable(db) - .await?; - - Ok(()) -} - #[db { new_struct_name = "CreditCardTransaction", - airtable_base = "finance", - airtable_table = "AIRTABLE_CREDIT_CARD_TRANSACTIONS_TABLE", match_on = { "cio_company_id" = "i32", "transaction_id" = "String", @@ -379,14 +257,6 @@ pub struct NewCreditCardTransaction { pub cio_company_id: i32, } -/// Implement updating the Airtable record for a CreditCardTransaction. -#[async_trait] -impl UpdateAirtableRecord for CreditCardTransaction { - async fn update_airtable_record(&mut self, _record: CreditCardTransaction) -> Result<()> { - Ok(()) - } -} - pub async fn refresh_ramp_transactions(db: &Database, company: &Company, config: &FinanceConfig) -> Result<()> { // Create the Ramp client. let ramp = company.authenticate_ramp()?; @@ -451,11 +321,6 @@ pub async fn refresh_ramp_transactions(db: &Database, company: &Company, config: nt.upsert(db).await?; } - CreditCardTransactions::get_from_db(db, company.id) - .await? - .update_airtable(db) - .await?; - Ok(()) } @@ -517,11 +382,6 @@ pub async fn refresh_ramp_reimbursements(db: &Database, company: &Company, confi nt.upsert(db).await?; } - ExpensedItems::get_from_db(db, company.id) - .await? - .update_airtable(db) - .await?; - Ok(()) } @@ -621,8 +481,6 @@ pub async fn refresh_brex_transactions(db: &Database, company: &Company, config: #[db { new_struct_name = "AccountsPayable", - airtable_base = "finance", - airtable_table = "AIRTABLE_ACCOUNTS_PAYABLE_TABLE", match_on = { "cio_company_id" = "i32", "confirmation_number" = "String", @@ -664,14 +522,6 @@ pub struct NewAccountsPayable { pub cio_company_id: i32, } -/// Implement updating the Airtable record for a AccountsPayable. -#[async_trait] -impl UpdateAirtableRecord for AccountsPayable { - async fn update_airtable_record(&mut self, _record: AccountsPayable) -> Result<()> { - Ok(()) - } -} - pub mod bill_com_date_format { use chrono::NaiveDate; use serde::{self, Deserialize, Deserializer}; @@ -694,51 +544,8 @@ pub mod bill_com_date_format { } } -/// Sync accounts payable. -pub async fn refresh_accounts_payable(db: &Database, company: &Company, config: &FinanceConfig) -> Result<()> { - // Get all the records from Airtable. - let results: Vec> = company - .authenticate_airtable(&company.airtable_base_id_finance) - .list_records(&AccountsPayable::airtable_table(), "Grid view", vec![]) - .await?; - for bill_record in results { - let mut bill: NewAccountsPayable = bill_record.fields.into(); - - let vendor = clean_vendor_name(&bill.vendor, config); - // Try to find the merchant in our list of vendors. - match SoftwareVendor::get_from_db(db, company.id, vendor.to_string()).await { - Some(v) => { - bill.link_to_vendor = vec![v.airtable_record_id.to_string()]; - } - None => { - info!("could not find vendor that matches {}", vendor); - } - } - - // Upsert the record in our database. - let mut db_bill = bill.upsert_in_db(db).await?; - - db_bill.cio_company_id = company.id; - - if db_bill.airtable_record_id.is_empty() { - db_bill.airtable_record_id = bill_record.id; - } - - db_bill.update(db).await?; - } - - AccountsPayables::get_from_db(db, company.id) - .await? - .update_airtable(db) - .await?; - - Ok(()) -} - #[db { new_struct_name = "ExpensedItem", - airtable_base = "finance", - airtable_table = "AIRTABLE_EXPENSED_ITEMS_TABLE", match_on = { "cio_company_id" = "i32", "transaction_id" = "String", @@ -791,22 +598,9 @@ pub struct NewExpensedItem { pub cio_company_id: i32, } -/// Implement updating the Airtable record for a ExpensedItem. -#[async_trait] -impl UpdateAirtableRecord for ExpensedItem { - async fn update_airtable_record(&mut self, _record: ExpensedItem) -> Result<()> { - Ok(()) - } -} - /// Read the Expensify transactions from a csv. /// We don't run this except locally. pub async fn refresh_expensify_transactions(db: &Database, company: &Company, config: &FinanceConfig) -> Result<()> { - ExpensedItems::get_from_db(db, company.id) - .await? - .update_airtable(db) - .await?; - let mut path = env::current_dir()?; path.push("expensify.csv"); @@ -1109,19 +903,19 @@ fn clean_merchant_name(merchant_name: &str, config: &FinanceConfig) -> String { } pub async fn refresh_all_finance(db: &Database, company: &Company, config: &FinanceConfig) -> Result<()> { - let (sv, reim, trans, ap, qb) = tokio::join!( - refresh_software_vendors(db, company), - refresh_ramp_reimbursements(db, company, config), - refresh_ramp_transactions(db, company, config), - refresh_accounts_payable(db, company, config), - sync_quickbooks(db, company, config), - ); - - sv?; - reim?; - trans?; - ap?; - qb?; + // let (sv, reim, trans, ap, qb) = tokio::join!( + // refresh_software_vendors(db, company), + // refresh_ramp_reimbursements(db, company, config), + // refresh_ramp_transactions(db, company, config), + // refresh_accounts_payable(db, company, config), + // sync_quickbooks(db, company, config), + // ); + + // sv?; + // reim?; + // trans?; + // ap?; + // qb?; Ok(()) } diff --git a/cio/src/functions.rs b/cio/src/functions.rs index 9990935ae..7bac9d5fc 100644 --- a/cio/src/functions.rs +++ b/cio/src/functions.rs @@ -20,8 +20,6 @@ use crate::{ #[db { new_struct_name = "Function", - airtable_base = "cio", - airtable_table = "AIRTABLE_FUNCTIONS_TABLE", match_on = { "saga_id" = "String", }, @@ -48,16 +46,6 @@ pub struct NewFunction { pub cio_company_id: i32, } -/// Implement updating the Airtable record for a Function. -#[async_trait] -impl UpdateAirtableRecord for Function { - async fn update_airtable_record(&mut self, _record: Function) -> Result<()> { - // Provide leeway in case this is causing log updates to fail - self.logs = truncate(&self.logs, 90_000); - Ok(()) - } -} - fn get_color_based_from_status_and_conclusion(status: &str, conclusion: &str) -> String { if status == octorust::types::JobStatus::InProgress.to_string() { return crate::colors::Colors::Blue.to_string(); @@ -425,11 +413,5 @@ pub async fn refresh_functions(db: &Database, company: &Company) -> Result<()> { new.send_slack_notification(db, company).await?; } - // AM: Disabling the bulk function updates. There are too many to actually update this way. - // If we want to continue this process we should first build a work plan that pairs down - // what data actually needs to be updated, and then running through that plan (ensuring it is) - // still relevant. This does not fully avoid race conditions, but could help. - // Functions::get_from_db(&db, 1).await?.update_airtable(&db).await?; - Ok(()) } diff --git a/cio/src/interviews.rs b/cio/src/interviews.rs index 7cd7d6213..d72f05851 100644 --- a/cio/src/interviews.rs +++ b/cio/src/interviews.rs @@ -34,8 +34,6 @@ use crate::{ #[db { new_struct_name = "ApplicantInterview", - airtable_base = "hiring", - airtable_table = "AIRTABLE_INTERVIEWS_TABLE", match_on = { "google_event_id" = "String", }, @@ -68,14 +66,6 @@ pub struct NewApplicantInterview { pub cio_company_id: i32, } -/// Implement updating the Airtable record for a ApplicantInterview. -#[async_trait] -impl UpdateAirtableRecord for ApplicantInterview { - async fn update_airtable_record(&mut self, _record: ApplicantInterview) -> Result<()> { - Ok(()) - } -} - /// Sync interviews. pub async fn refresh_interviews(db: &Database, company: &Company) -> Result<()> { if company.airtable_base_id_hiring.is_empty() { @@ -319,11 +309,6 @@ pub async fn refresh_interviews(db: &Database, company: &Company) -> Result<()> } } - ApplicantInterviews::get_from_db(db, company.id) - .await? - .update_airtable(db) - .await?; - Ok(()) } diff --git a/cio/src/journal_clubs.rs b/cio/src/journal_clubs.rs index e00c5482a..eb742bc2e 100644 --- a/cio/src/journal_clubs.rs +++ b/cio/src/journal_clubs.rs @@ -22,8 +22,6 @@ use crate::{ /// The data type for a NewJournalClubMeeting. #[db { new_struct_name = "JournalClubMeeting", - airtable_base = "misc", - airtable_table = "AIRTABLE_JOURNAL_CLUB_MEETINGS_TABLE", match_on = { "issue" = "String", }, @@ -145,22 +143,9 @@ impl From for FormattedMessage { } } -/// Implement updating the Airtable record for a JournalClubMeeting. -#[async_trait] -impl UpdateAirtableRecord for JournalClubMeeting { - async fn update_airtable_record(&mut self, record: JournalClubMeeting) -> Result<()> { - // Set the papers field, since it is pre-populated as table links. - self.papers = record.papers; - - Ok(()) - } -} - /// The data type for a NewJournalClubPaper. #[db { new_struct_name = "JournalClubPaper", - airtable_base = "misc", - airtable_table = "AIRTABLE_JOURNAL_CLUB_PAPERS_TABLE", match_on = { "link" = "String", }, @@ -181,29 +166,6 @@ pub struct NewJournalClubPaper { pub cio_company_id: i32, } -/// Implement updating the Airtable record for a JournalClubPaper. -#[async_trait] -impl UpdateAirtableRecord for JournalClubPaper { - async fn update_airtable_record(&mut self, _record: JournalClubPaper) -> Result<()> { - // Get the current journal club meetings in Airtable so we can link to it. - // TODO: make this more dry so we do not call it every single damn time. - let db = Database::new().await; - let journal_club_meetings = JournalClubMeetings::get_from_airtable(&db, self.cio_company_id).await?; - - // Iterate over the journal_club_meetings and see if we find a match. - for (_id, meeting_record) in journal_club_meetings { - if meeting_record.fields.issue == self.meeting { - // Set the link_to_meeting to the right meeting. - self.link_to_meeting = vec![meeting_record.id]; - // Break the loop and return early. - break; - } - } - - Ok(()) - } -} - #[derive(Debug, Deserialize, Serialize)] pub struct Meeting { pub title: String, @@ -352,15 +314,5 @@ pub async fn refresh_db_journal_club_meetings(db: &Database, company: &Company) } } - JournalClubPapers::get_from_db(db, company.id) - .await? - .update_airtable(db) - .await?; - - JournalClubMeetings::get_from_db(db, company.id) - .await? - .update_airtable(db) - .await?; - Ok(()) } diff --git a/cio/src/mailing_list.rs b/cio/src/mailing_list.rs index e48709a34..5b5b5d26f 100644 --- a/cio/src/mailing_list.rs +++ b/cio/src/mailing_list.rs @@ -17,8 +17,6 @@ use crate::{ /// The data type for a MailingListSubscriber. #[db { new_struct_name = "MailingListSubscriber", - airtable_base = "customer_leads", - airtable_table = "AIRTABLE_MAILING_LIST_SIGNUPS_TABLE", match_on = { "email" = "String", }, @@ -224,17 +222,6 @@ impl Default for NewMailingListSubscriber { } } -/// Implement updating the Airtable record for a MailingListSubscriber. -#[async_trait] -impl UpdateAirtableRecord for MailingListSubscriber { - async fn update_airtable_record(&mut self, record: MailingListSubscriber) -> Result<()> { - // Set the link_to_people from the original so it stays intact. - self.link_to_people = record.link_to_people; - - Ok(()) - } -} - impl From for NewMailingListSubscriber { fn from(subscriber: mailerlite::Subscriber) -> Self { let mut new_sub = NewMailingListSubscriber::default(); diff --git a/cio/src/rack_line.rs b/cio/src/rack_line.rs index 9ebcaaca6..289913418 100644 --- a/cio/src/rack_line.rs +++ b/cio/src/rack_line.rs @@ -17,8 +17,6 @@ use crate::{ /// The data type for a RackLineSubscriber. #[db { new_struct_name = "RackLineSubscriber", - airtable_base = "customer_leads", - airtable_table = "AIRTABLE_RACK_LINE_SIGNUPS_TABLE", match_on = { "email" = "String", }, @@ -180,25 +178,6 @@ impl Default for NewRackLineSubscriber { } } -/// Implement updating the Airtable record for a RackLineSubscriber. -#[async_trait] -impl UpdateAirtableRecord for RackLineSubscriber { - async fn update_airtable_record(&mut self, record: RackLineSubscriber) -> Result<()> { - // Set the link_to_people from the original so it stays intact. - self.link_to_people = record.link_to_people; - - // Notes and tags are owned by Airtable - self.notes = record.notes; - self.tags = record.tags; - - // Exclusions are owned by Airtable - self.zoho_lead_exclude = record.zoho_lead_exclude; - self.sf_lead_exclude = record.sf_lead_exclude; - - Ok(()) - } -} - impl From for NewRackLineSubscriber { fn from(subscriber: mailerlite::Subscriber) -> Self { let mut new_sub = NewRackLineSubscriber::default(); diff --git a/cio/src/recorded_meetings.rs b/cio/src/recorded_meetings.rs index bfcb90cf5..a2c5d6347 100644 --- a/cio/src/recorded_meetings.rs +++ b/cio/src/recorded_meetings.rs @@ -30,8 +30,6 @@ use crate::{ /// The data type for a recorded meeting. #[db { new_struct_name = "RecordedMeeting", - airtable_base = "misc", - airtable_table = "AIRTABLE_RECORDED_MEETINGS_TABLE", match_on = { "google_event_id" = "String", }, @@ -74,23 +72,6 @@ pub struct NewRecordedMeeting { pub cio_company_id: i32, } -/// Implement updating the Airtable record for a RecordedMeeting. -#[async_trait] -impl UpdateAirtableRecord for RecordedMeeting { - async fn update_airtable_record(&mut self, record: RecordedMeeting) -> Result<()> { - if !record.transcript_id.is_empty() { - self.transcript_id = record.transcript_id; - } - if !record.transcript.is_empty() { - self.transcript = record.transcript; - } - - self.transcript = truncate(&self.transcript, 100000); - - Ok(()) - } -} - /// Convert the recorded meeting into a Slack message. impl From for FormattedMessage { fn from(item: NewRecordedMeeting) -> Self { @@ -690,16 +671,6 @@ pub async fn refresh_google_recorded_meetings(db: &Database, company: &Company) // Update the meeting. meeting.transcript = m.transcript.to_string(); meeting.transcript_id = m.transcript_id.to_string(); - - // Get it from Airtable. - if let Some(existing_airtable) = m.get_existing_airtable_record(db).await { - if meeting.transcript.is_empty() { - meeting.transcript = existing_airtable.fields.transcript.to_string(); - } - if meeting.transcript_id.is_empty() { - meeting.transcript_id = existing_airtable.fields.transcript_id.to_string(); - } - } } else { // We have a new meeting, let's send the notification. let _ = meeting.send_slack_notification(db, company).await.map_err(|err| { @@ -716,11 +687,6 @@ pub async fn refresh_google_recorded_meetings(db: &Database, company: &Company) } } - RecordedMeetings::get_from_db(db, company.id) - .await? - .update_airtable(db) - .await?; - Ok(()) } diff --git a/cio/src/repos.rs b/cio/src/repos.rs index 24057f157..c47638209 100644 --- a/cio/src/repos.rs +++ b/cio/src/repos.rs @@ -66,8 +66,6 @@ pub struct GitHubUser { /// The data type for a GitHub repository. #[db { new_struct_name = "GithubRepo", - airtable_base = "misc", - airtable_table = "AIRTABLE_GITHUB_REPOS_TABLE", match_on = { "github_id" = "String", }, @@ -211,14 +209,6 @@ pub struct NewRepo { pub cio_company_id: i32, } -/// Implement updating the Airtable record for a GithubRepo. -#[async_trait] -impl UpdateAirtableRecord for GithubRepo { - async fn update_airtable_record(&mut self, _record: GithubRepo) -> Result<()> { - Ok(()) - } -} - impl NewRepo { pub fn new_from_full(r: octorust::types::FullRepository, cio_company_id: i32) -> Self { NewRepo { @@ -581,11 +571,6 @@ pub async fn refresh_db_github_repos(db: &Database, company: &Company) -> Result repo.delete(db).await?; } - GithubRepos::get_from_db(db, company.id) - .await? - .update_airtable(db) - .await?; - Ok(()) } diff --git a/cio/src/rfd/model.rs b/cio/src/rfd/model.rs index 2b09dbab9..43d8a6b95 100644 --- a/cio/src/rfd/model.rs +++ b/cio/src/rfd/model.rs @@ -25,8 +25,6 @@ use crate::{ #[db { target_struct = "NewRFD", new_struct_name = "RFD", - airtable_base = "roadmap", - airtable_table = "AIRTABLE_RFD_TABLE", match_on = { "number" = "i32", } @@ -335,18 +333,3 @@ impl RFD { Ok(update) } } - -/// Implement updating the Airtable record for an RFD. -#[async_trait] -impl UpdateAirtableRecord for RFD { - async fn update_airtable_record(&mut self, record: RFD) -> Result<()> { - // Set the Link to People from the original so it stays intact. - self.milestones = record.milestones.clone(); - self.relevant_components = record.relevant_components; - - self.content = truncate(&self.content, 100000); - self.html = "".to_string(); - - Ok(()) - } -} diff --git a/cio/src/shipments.rs b/cio/src/shipments.rs index 8ea6d0797..149e5d034 100644 --- a/cio/src/shipments.rs +++ b/cio/src/shipments.rs @@ -31,8 +31,6 @@ use crate::{ /// The data type for an inbound shipment. #[db { new_struct_name = "InboundShipment", - airtable_base = "shipments", - airtable_table = "AIRTABLE_INBOUND_TABLE", match_on = { "carrier" = "String", "tracking_number" = "String", @@ -73,39 +71,6 @@ pub struct NewInboundShipment { pub cio_company_id: i32, } -/// Implement updating the Airtable record for an InboundShipment. -#[async_trait] -impl UpdateAirtableRecord for InboundShipment { - async fn update_airtable_record(&mut self, record: InboundShipment) -> Result<()> { - if self.carrier.is_empty() { - self.carrier = record.carrier; - } - if self.tracking_number.is_empty() { - self.tracking_number = record.tracking_number; - } - if self.tracking_link.is_empty() { - self.tracking_link = record.tracking_link; - } - if self.tracking_status.is_empty() { - self.tracking_status = record.tracking_status; - } - if self.shipped_time.is_none() { - self.shipped_time = record.shipped_time; - } - if self.delivered_time.is_none() { - self.delivered_time = record.delivered_time; - } - if self.eta.is_none() { - self.eta = record.eta; - } - if self.notes.is_empty() { - self.notes = record.notes; - } - - Ok(()) - } -} - impl NewInboundShipment { pub fn oxide_tracking_link(&self) -> String { format!("https://track.oxide.computer/{}/{}", self.carrier, self.tracking_number) @@ -361,8 +326,6 @@ impl InboundShipment { /// The data type for an outbound shipment. #[db { new_struct_name = "OutboundShipment", - airtable_base = "shipments", - airtable_table = "AIRTABLE_OUTBOUND_TABLE", match_on = { "carrier" = "String", "tracking_number" = "String", @@ -706,8 +669,6 @@ impl From for FormattedMessage { /// The data type for a shipment pickup. #[db { new_struct_name = "PackagePickup", - airtable_base = "shipments", - airtable_table = "AIRTABLE_PACKAGE_PICKUPS_TABLE", match_on = { "shippo_id" = "String", }, @@ -744,14 +705,6 @@ pub struct NewPackagePickup { pub cio_company_id: i32, } -/// Implement updating the Airtable record for an PackagePickup. -#[async_trait] -impl UpdateAirtableRecord for PackagePickup { - async fn update_airtable_record(&mut self, _record: PackagePickup) -> Result<()> { - Ok(()) - } -} - impl OutboundShipments { // Always schedule the pickup for the next business day. // It will create a pickup for all the shipments that have "Label printed" @@ -902,58 +855,6 @@ pub fn get_next_business_day() -> (DateTime, DateTime) { ) } -/// Implement updating the Airtable record for an OutboundShipment. -#[async_trait] -impl UpdateAirtableRecord for OutboundShipment { - async fn update_airtable_record(&mut self, record: OutboundShipment) -> Result<()> { - self.link_to_package_pickup = record.link_to_package_pickup; - - self.geocode_cache = record.geocode_cache; - - if self.status.is_empty() { - self.status = record.status; - } - if self.carrier.is_empty() { - self.carrier = record.carrier; - } - if self.tracking_number.is_empty() { - self.tracking_number = record.tracking_number; - } - if self.tracking_link.is_empty() { - self.tracking_link = record.tracking_link; - } - if self.tracking_status.is_empty() { - self.tracking_status = record.tracking_status; - } - if self.label_link.is_empty() { - self.label_link = record.label_link; - } - if self.pickup_date.is_none() { - self.pickup_date = record.pickup_date; - } - if self.shipped_time.is_none() { - self.shipped_time = record.shipped_time; - } - if self.delivered_time.is_none() { - self.delivered_time = record.delivered_time; - } - if self.provider_id.is_empty() { - self.provider_id = record.provider_id; - } - if self.eta.is_none() { - self.eta = record.eta; - } - if self.cost == 0.0 { - self.cost = record.cost; - } - if self.notes.is_empty() { - self.notes = record.notes; - } - - Ok(()) - } -} - impl OutboundShipment { fn populate_formatted_address(&mut self) { let mut street_address = self.street_1.to_string(); @@ -1644,10 +1545,6 @@ pub async fn refresh_outbound_shipments(db: &Database, company: &Company) -> Res // we do not. let shipments = OutboundShipments::get_from_db(db, company.id).await?; for mut s in shipments { - if let Some(existing) = s.get_existing_airtable_record(db).await { - // Take the field from Airtable. - s.local_pickup = existing.fields.local_pickup; - } // Update the shipment from shippo, this will only apply if the provider is set as "Shippo". s.create_or_get_shippo_shipment(db).await?; @@ -1658,10 +1555,6 @@ pub async fn refresh_outbound_shipments(db: &Database, company: &Company) -> Res update_manual_shippo_shipments(db, company).await?; - OutboundShipments::get_from_db(db, company.id) - .await? - .update_airtable(db) - .await?; Ok(()) } @@ -1752,7 +1645,7 @@ async fn update_manual_shippo_shipments(db: &Database, company: &Company) -> Res } // Upsert the record in the database. - let mut s = ns.upsert_in_db(db).await?; + let mut s = ns.upsert(db).await?; // The shipment is actually new, lets send the notification for the status // as queued then. @@ -1767,42 +1660,6 @@ async fn update_manual_shippo_shipments(db: &Database, company: &Company) -> Res Ok(()) } -// Sync the inbound shipments. -pub async fn refresh_inbound_shipments(db: &Database, company: &Company) -> Result<()> { - if company.airtable_base_id_shipments.is_empty() { - // Return early. - return Ok(()); - } - - let is: Vec> = company - .authenticate_airtable(&company.airtable_base_id_shipments) - .list_records(&InboundShipment::airtable_table(), "Grid view", vec![]) - .await?; - - for record in is { - if record.fields.carrier.is_empty() || record.fields.tracking_number.is_empty() { - // Ignore it, it's a blank record. - continue; - } - - let mut new_shipment: NewInboundShipment = record.fields.into(); - new_shipment.expand().await?; - new_shipment.cio_company_id = company.id; - let mut shipment = new_shipment.upsert_in_db(db).await?; - if shipment.airtable_record_id.is_empty() { - shipment.airtable_record_id = record.id; - } - shipment.update(db).await?; - } - - InboundShipments::get_from_db(db, company.id) - .await? - .update_airtable(db) - .await?; - - Ok(()) -} - pub fn clean_address_string(s: &str) -> String { if s == "DE" { return "Germany".to_string(); diff --git a/cio/src/swag_inventory.rs b/cio/src/swag_inventory.rs index e6215825f..cb6e0f281 100644 --- a/cio/src/swag_inventory.rs +++ b/cio/src/swag_inventory.rs @@ -37,8 +37,6 @@ const DPI: f64 = 300.0; #[db { new_struct_name = "SwagItem", - airtable_base = "swag", - airtable_table = "AIRTABLE_SWAG_ITEMS_TABLE", match_on = { "name" = "String", }, @@ -75,54 +73,8 @@ pub struct NewSwagItem { pub cio_company_id: i32, } -/// Implement updating the Airtable record for a SwagItem. -#[async_trait] -impl UpdateAirtableRecord for SwagItem { - async fn update_airtable_record(&mut self, record: SwagItem) -> Result<()> { - if !record.link_to_inventory.is_empty() { - self.link_to_inventory = record.link_to_inventory; - } - if !record.link_to_barcode_scans.is_empty() { - self.link_to_barcode_scans = record.link_to_barcode_scans; - } - - Ok(()) - } -} - -/// Sync swag items from Airtable. -pub async fn refresh_swag_items(db: &Database, company: &Company) -> Result<()> { - if company.airtable_base_id_swag.is_empty() { - // Return early. - return Ok(()); - } - - // Get all the records from Airtable. - let results: Vec> = company - .authenticate_airtable(&company.airtable_base_id_swag) - .list_records(&SwagItem::airtable_table(), "Grid view", vec![]) - .await?; - for item_record in results { - let mut item: NewSwagItem = item_record.fields.into(); - item.cio_company_id = company.id; - - let mut db_item = item.upsert_in_db(db).await?; - db_item.airtable_record_id = item_record.id.to_string(); - db_item.update(db).await?; - } - - SwagItems::get_from_db(db, company.id) - .await? - .update_airtable(db) - .await?; - - Ok(()) -} - #[db { new_struct_name = "SwagInventoryItem", - airtable_base = "swag", - airtable_table = "AIRTABLE_SWAG_INVENTORY_ITEMS_TABLE", match_on = { "item" = "String", "size" = "String", @@ -179,24 +131,6 @@ pub struct NewSwagInventoryItem { pub cio_company_id: i32, } -/// Implement updating the Airtable record for a SwagInventoryItem. -#[async_trait] -impl UpdateAirtableRecord for SwagInventoryItem { - async fn update_airtable_record(&mut self, record: SwagInventoryItem) -> Result<()> { - if !record.link_to_item.is_empty() { - self.link_to_item = record.link_to_item; - } - - // This is a funtion in Airtable so we can't update it. - self.name = "".to_string(); - - // This is set in airtable so we need to keep it. - self.print_barcode_label_quantity = record.print_barcode_label_quantity; - - Ok(()) - } -} - impl NewSwagInventoryItem { pub async fn send_slack_notification(&self, db: &Database, company: &Company) -> Result<()> { let mut msg: FormattedMessage = self.clone().into(); @@ -611,53 +545,8 @@ impl SwagInventoryItem { } } -/// Sync swag inventory items from Airtable. -pub async fn refresh_swag_inventory_items(db: &Database, company: &Company) -> Result<()> { - if company.airtable_base_id_swag.is_empty() { - // Return early. - return Ok(()); - } - - // Initialize the Google Drive client. - let drive_client = company.authenticate_google_drive(db).await?; - - // Figure out where our directory is. - // It should be in the shared drive : "Automated Documents"/"rfds" - let shared_drive = drive_client.drives().get_by_name("Automated Documents").await?.body; - let drive_id = shared_drive.id.to_string(); - - // Get the directory by the name. - let parent_id = drive_client.files().create_folder(&drive_id, "", "swag").await?.body.id; - - // Get all the records from Airtable. - let results: Vec> = company - .authenticate_airtable(&company.airtable_base_id_swag) - .list_records(&SwagInventoryItem::airtable_table(), "Grid view", vec![]) - .await?; - for inventory_item_record in results { - let mut inventory_item: NewSwagInventoryItem = inventory_item_record.fields.into(); - inventory_item.expand(&drive_client, &drive_id, &parent_id).await?; - inventory_item.cio_company_id = company.id; - - // TODO: send a slack notification for a new item (?) - - let mut db_inventory_item = inventory_item.upsert_in_db(db).await?; - db_inventory_item.airtable_record_id = inventory_item_record.id.to_string(); - db_inventory_item.update(db).await?; - } - - SwagInventoryItems::get_from_db(db, company.id) - .await? - .update_airtable(db) - .await?; - - Ok(()) -} - #[db { new_struct_name = "BarcodeScan", - airtable_base = "swag", - airtable_table = "AIRTABLE_BARCODE_SCANS_TABLE", match_on = { "item" = "String", "size" = "String", @@ -689,14 +578,6 @@ pub struct NewBarcodeScan { pub cio_company_id: i32, } -/// Implement updating the Airtable record for a BarcodeScan. -#[async_trait] -impl UpdateAirtableRecord for BarcodeScan { - async fn update_airtable_record(&mut self, _record: BarcodeScan) -> Result<()> { - Ok(()) - } -} - impl BarcodeScan { // Takes a scanned barcode and updates the inventory count for the item // as well as adds the scan to the barcodes_scan table for tracking. @@ -747,17 +628,3 @@ impl BarcodeScan { Ok(()) } } - -pub async fn refresh_barcode_scans(db: &Database, company: &Company) -> Result<()> { - if company.airtable_base_id_swag.is_empty() { - // Return early. - return Ok(()); - } - - BarcodeScans::get_from_db(db, company.id) - .await? - .update_airtable(db) - .await?; - - Ok(()) -} diff --git a/cio/src/swag_store.rs b/cio/src/swag_store.rs index ca2f07a96..e257ff28d 100644 --- a/cio/src/swag_store.rs +++ b/cio/src/swag_store.rs @@ -67,7 +67,7 @@ impl Order { let shipment: NewOutboundShipment = self.to_outbound_shipment().await?; // Add the shipment to the database. - let mut new_shipment = shipment.upsert_in_db(db).await?; + let mut new_shipment = shipment.upsert(db).await?; // Create or update the shipment from shippo. new_shipment.create_or_get_shippo_shipment(db).await?; // Update airtable and the database again. diff --git a/cio/src/travel.rs b/cio/src/travel.rs index 8bef4e6eb..93cb95dea 100644 --- a/cio/src/travel.rs +++ b/cio/src/travel.rs @@ -12,8 +12,6 @@ use crate::{ #[db { new_struct_name = "Booking", - airtable_base = "travel", - airtable_table = "AIRTABLE_BOOKINGS_TABLE", match_on = { "cio_company_id" = "i32", "booking_id" = "String", @@ -85,14 +83,6 @@ pub struct NewBooking { pub cio_company_id: i32, } -/// Implement updating the Airtable record for a Booking. -#[async_trait] -impl UpdateAirtableRecord for Booking { - async fn update_airtable_record(&mut self, _record: Booking) -> Result<()> { - Ok(()) - } -} - pub async fn refresh_trip_actions(db: &Database, company: &Company) -> Result<()> { // Authenticate with TripActions. let tripactions_auth = company.authenticate_tripactions(db).await; diff --git a/macros/src/lib.rs b/macros/src/lib.rs index d5db9082a..a96dd3530 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -20,12 +20,6 @@ struct Params { /// - id: i32 /// - airtable_record_id: String new_struct_name: String, - /// The name of the table in Airtable where this information should be sync on every - /// database operation. - airtable_table: String, - /// The Airtable base where this information should be sync on every - /// database operation. - airtable_base: String, /// A boolean representing if the new struct has a custom PartialEq implementation. /// If so, we will not add the derive method PartialEq to the new struct. #[serde(default)] @@ -85,31 +79,13 @@ fn do_db(attr: TokenStream, item: TokenStream) -> TokenStream { } let og_struct_name = og_struct.ident; - // Get the Airtable information. - let airtable_base = format_ident!("airtable_base_id_{}", params.airtable_base); - let airtable_table = format_ident!("{}", params.airtable_table); - - let airtable = quote! { + let db_impl = quote! { // Import what we need from diesel so the database queries work. use diesel::prelude::*; impl #og_struct_name { /// Create a new record in the database and Airtable. pub async fn create(&self, db: &crate::db::Database) -> anyhow::Result<#new_struct_name> { - let mut new_record = self.create_in_db(db).await?; - - // Let's also create this record in Airtable. - let new_airtable_record = new_record.create_in_airtable(db).await?; - - // Now we have the id we need to update the database. - new_record.airtable_record_id = new_airtable_record.id.to_string(); - let r = new_record.update_in_db(db).await?; - Ok(r) - } - - /// Create a new record in the database. - pub async fn create_in_db(&self, db: &crate::db::Database) -> anyhow::Result<#new_struct_name> { - // // TODO: special error here. let r = diesel::insert_into(crate::schema::#db_schema::table) .values(self.clone()) .get_result_async(db.pool()).await?; @@ -119,32 +95,6 @@ fn do_db(attr: TokenStream, item: TokenStream) -> TokenStream { /// Create or update the record in the database and Airtable. pub async fn upsert(&self, db: &crate::db::Database) -> anyhow::Result<#new_struct_name> { - let mut record = self.upsert_in_db(db).await?; - - log::info!("Upserted {} into database. Upserting to Airtable", #new_struct_name_str); - - // Let's also update this record in Airtable. - let new_airtable_record = match record.upsert_in_airtable(db).await { - Ok(airtable_record) => airtable_record, - Err(err) => { - log::error!("Failed to upsert persisted database record into Airtable. id: {}", record.id); - return Err(err); - } - }; - - log::info!("Upserted {} record to Airtable", #new_struct_name_str); - - if record.airtable_record_id.is_empty(){ - // Now we have the id we need to update the database. - record.airtable_record_id = new_airtable_record.id.to_string(); - return record.update_in_db(db).await; - } - - Ok(record) - } - - /// Create or update the record in the database. - pub async fn upsert_in_db(&self, db: &crate::db::Database) -> anyhow::Result<#new_struct_name> { log::info!("Upserting {} record", #new_struct_name_str); // See if we already have the record in the database. @@ -163,7 +113,7 @@ fn do_db(attr: TokenStream, item: TokenStream) -> TokenStream { log::info!("No existing {} record. Performing create", #new_struct_name_str); - let r = self.create_in_db(db).await?; + let r = self.create(db).await?; Ok(r) } @@ -196,19 +146,6 @@ fn do_db(attr: TokenStream, item: TokenStream) -> TokenStream { impl #new_struct_name { /// Update the record in the database and Airtable. pub async fn update(&self, db: &crate::db::Database) -> anyhow::Result { - // Update the record. - let mut record = self.update_in_db(db).await?; - - // Let's also update this record in Airtable. - let new_airtable_record = record.upsert_in_airtable(db).await?; - - // Now we have the id we need to update the database. - record.airtable_record_id = new_airtable_record.id.to_string(); - record.update_in_db(db).await - } - - /// Update the record in the database. - pub async fn update_in_db(&self, db: &crate::db::Database) -> anyhow::Result { // Update the record. let record = diesel::update(#db_schema::dsl::#db_schema) .filter(#db_schema::dsl::id.eq(self.id)) @@ -239,6 +176,14 @@ fn do_db(attr: TokenStream, item: TokenStream) -> TokenStream { Ok(record) } + /// Get a record by its airtable id. + pub async fn get_by_airtable_id(db: &crate::db::Database, id: &str) -> anyhow::Result { + let record = #db_schema::dsl::#db_schema.filter(#db_schema::dsl::airtable_record_id.eq(id.to_string())) + .first_async::<#new_struct_name>(db.pool()).await?; + + Ok(record) + } + /// Get the company object for a record. pub async fn company(&self, db: &crate::db::Database) -> anyhow::Result { match crate::companies::Company::get_by_id(db, self.cio_company_id).await { @@ -247,27 +192,8 @@ fn do_db(attr: TokenStream, item: TokenStream) -> TokenStream { } } - /// Get the row in our airtable workspace. - pub async fn get_from_airtable(id: &str, db: &crate::db::Database, cio_company_id: i32) -> anyhow::Result { - let record = #new_struct_name::airtable_from_company_id(db, cio_company_id).await? - .get_record(&#new_struct_name::airtable_table(), id) - .await?; - - Ok(record.fields) - } - /// Delete a record from the database and Airtable. pub async fn delete(&self, db: &crate::db::Database) -> anyhow::Result<()> { - self.delete_from_db(db).await?; - - // Let's also delete the record from Airtable. - self.delete_from_airtable(db).await?; - - Ok(()) - } - - /// Delete a record from the database. - pub async fn delete_from_db(&self, db: &crate::db::Database) -> anyhow::Result<()> { diesel::delete( crate::schema::#db_schema::dsl::#db_schema.filter( crate::schema::#db_schema::dsl::id.eq(self.id))) @@ -275,168 +201,6 @@ fn do_db(attr: TokenStream, item: TokenStream) -> TokenStream { Ok(()) } - - /// Create the Airtable client. - /// We do this in it's own function so our other functions are more DRY. - async fn airtable(&self, db: &crate::db::Database) -> anyhow::Result { - // Get the company for the company_id. - let company = self.company(db).await?; - Ok(company.authenticate_airtable(&company.#airtable_base)) - } - - /// Create the Airtable client. - /// We do this in it's own function so our other functions are more DRY. - async fn airtable_from_company_id(db: &crate::db::Database, cio_company_id: i32) -> anyhow::Result { - // Get the company for the company_id. - let company = crate::companies::Company::get_by_id(db, cio_company_id).await?; - Ok(company.authenticate_airtable(&company.#airtable_base)) - } - - /// Return the Airtable table name. - /// We do this in it's own function so our other functions are more DRY. - fn airtable_table() -> String { - #airtable_table.to_string() - } - - /// Create the row in the Airtable base. - pub async fn create_in_airtable(&mut self, db: &crate::db::Database) -> anyhow::Result> { - let mut mut_self = self.clone(); - // Run the custom trait to update the new record from the old record. - // We do this because where we join Airtable tables, things tend to get a little - // weird if we aren't nit picky about this. - mut_self.update_airtable_record(self.clone()).await?; - - // Create the record. - let record = airtable_api::Record { - id: "".to_string(), - created_time: None, - fields: mut_self, - }; - - // Send the new record to the Airtable client. - let records : Vec> = self.airtable(db).await? - .create_records(&#new_struct_name::airtable_table(), vec![record]) - .await - ?; - - log::info!("[airtable] created new row: {:?}", self); - - // Return the first record back. - Ok(records.get(0).unwrap().clone()) - } - - /// Update the record in Airtable. - pub async fn update_in_airtable(&self, db: &crate::db::Database, existing_record: &mut airtable_api::Record<#new_struct_name>) -> anyhow::Result> { - let mut mut_self = self.clone(); - // Run the custom trait to update the new record from the old record. - // We do this because where we join Airtable tables, things tend to get a little - // weird if we aren't nit picky about this. - mut_self.update_airtable_record(existing_record.fields.clone()).await?; - - // If the Airtable record and the record that was passed in are the same, then we can return early since - // we do not need to update it in Airtable. - // We do this after we update the record so that any fields that are links to other - // tables match as well and this can return true even if we have linked records. - if mut_self == existing_record.fields { - log::info!("[airtable] id={} table={} in given object equals Airtable record, skipping update", self.id, #new_struct_name::airtable_table()); - return Ok(existing_record.clone()); - } - - existing_record.fields = mut_self; - - // Send the updated record to Airtable. - let records : Vec> = self.airtable(db).await?.update_records( - &#new_struct_name::airtable_table(), - vec![existing_record.clone()], - ).await?; - - log::info!("[airtable] id={} table={} updated", self.id, #new_struct_name::airtable_table()); - - if records.is_empty() { - return Ok(existing_record.clone()); - } - - Ok(records.get(0).unwrap().clone()) - } - - /// Get the existing record in Airtable that matches this id. - pub async fn get_existing_airtable_record(&self, db: &crate::db::Database) -> Option> { - if self.airtable_record_id.is_empty() { - return None; - } - // Let's get the existing record from airtable. - if let Ok(a) = self.airtable(db).await { - match a.get_record(&#new_struct_name::airtable_table(), &self.airtable_record_id) - .await { - Ok(v) => return Some(v), - Err(e) => { - log::info!("getting airtable record failed: {}", self.airtable_record_id); - return None; - } - } - } - - None - } - - /// Create or update a row in the Airtable base. - pub async fn upsert_in_airtable(&mut self, db: &crate::db::Database) -> anyhow::Result> { - // First check if we have an `airtable_record_id` for this record. - // If we do we can move ahead faster. - if !self.airtable_record_id.is_empty() { - log::info!("Attempt to perform update of Airtable {}", #new_struct_name_str); - let mut er: Option> = self.get_existing_airtable_record(db).await; - - if let Some(mut existing_record) = er { - // Return the result from the update. - return self.update_in_airtable(db, &mut existing_record).await; - } - // Otherwise we need to continue through the other loop. - } - - log::info!("Falling back to very slow Airtable lookup for {} record", #new_struct_name_str); - - // Since we don't know the airtable record id, we need to find it by looking - // through all the existing records in Airtable and matching on our database id. - // This is slow so we should always try to make sure we have the airtable_record_id - // set. This function is mostly here until we migrate away from the old way of doing - // things. - let mut records = #new_struct_name_plural::get_records_from_airtable(db, self.cio_company_id, &[self.id]).await?; - - if records.len() == 1 { - if let Some(mut record) = records.get_mut(&self.id) { - return self.update_in_airtable(db, record).await; - } else { - return Err(anyhow::anyhow!("Invalid airtable record returned for record {}", self.id)) - } - } - - log::info!("Record does not exist in Airtable. Creating new {} record", #new_struct_name_str); - - // We've tried everything to find the record in our existing Airtable but it is not - // there. We need to create it. - let record = self.create_in_airtable(db).await?; - - Ok(record) - } - - /// Delete a record from Airtable. - pub async fn delete_from_airtable(&self, db: &crate::db::Database) -> anyhow::Result<()> { - if !self.airtable_record_id.is_empty() { - // Delete the record from airtable. - if let Err(e) = self.airtable(db).await?.delete_record(&#new_struct_name::airtable_table(), &self.airtable_record_id).await { - // Ignore if we got a NOT_FOUND error since then the record does not exist. - if e.to_string().contains("NOT_FOUND") { - return Ok(()); - } - - // Otherwise return the actual error. - anyhow::bail!("deleting record: `{:?}` from Airtable failed: {}", self, e); - } - } - - Ok(()) - } } #[derive(Debug, Clone, Deserialize, Serialize)] @@ -470,107 +234,6 @@ fn do_db(attr: TokenStream, item: TokenStream) -> TokenStream { Err(e) => Err(anyhow::anyhow!("getting `{:?}` from the database for cio_company_id `{}` failed: {}", #new_struct_name_plural(vec![]), cio_company_id, e)), } } - - /// Get the current records for this type from Airtable. - pub async fn get_from_airtable(db: &crate::db::Database, cio_company_id: i32) -> anyhow::Result>> { - let result: Vec> = #new_struct_name::airtable_from_company_id(db, cio_company_id).await? - .list_records(&#new_struct_name::airtable_table(), "Grid view", vec![]) - .await?; - - let mut records: std::collections::BTreeMap> = - Default::default(); - for record in result { - records.insert(record.fields.id, record); - } - - Ok(records) - } - - pub async fn get_records_from_airtable(db: &crate::db::Database, cio_company_id: i32, ids: &[i32]) -> anyhow::Result>> { - let client = #new_struct_name::airtable_from_company_id(db, cio_company_id).await?; - let mut pages = client.pages::<#new_struct_name>(&#new_struct_name::airtable_table(), "Grid view", vec![]); - let mut airtable_records: std::collections::BTreeMap> = Default::default(); - - while let Some(records) = pages.next().await? { - for record in records { - if ids.contains(&record.fields.id) { - airtable_records.insert(record.fields.id, record); - } - } - - // If we find a record for every requested id we can return early - if airtable_records.len() == ids.len() { - return Ok(airtable_records); - } - } - - Ok(airtable_records) - } - - /// Update Airtable records in a table from a vector. - pub async fn update_airtable(&self, db: &crate::db::Database) -> anyhow::Result<()> { - use anyhow::Context; - - if self.0.is_empty() { - // Return early. - return Ok(()); - } - - let ids = self.0.iter().map(|r| r.id).collect::>(); - let mut airtable_records = #new_struct_name_plural::get_records_from_airtable(db, self.0.get(0).unwrap().cio_company_id, &ids).await?; - - for record in &self.0 { - // Get the latest of this record from the database. - // This make this less racy if we have a bunch and things got - // out of sync. - if let Ok(mut db_record) = #new_struct_name::get_by_id(db, record.id).await { - // See if we have it in our Airtable records. - match airtable_records.get_mut(&db_record.id) { - Some(airtable_record) => { - - // Update the record in Airtable. - match db_record.update_in_airtable(db, airtable_record).await { - // We do not need to do anything else with the record once it is updated - Ok(_record) => (), - Err(err) => { - // We need to log the error so that we can address the underlying issue, but - // we do not want to abandon all of the rest of the record updates when a - // single record fails to be written - log::error!("Failed to update already persisted Airtable record (internal record: {} Airtable record: {})", db_record.id, record.id); - log::error!("Failed to update a record in airtable: {}", err); - } - }; - - // Remove it from the map. - airtable_records.remove(&db_record.id); - } - None => { - // We do not have the record in Airtable, Let's create it. - // Create the record in Airtable. - // db_record.create_in_airtable(db).await?; - - // AM: Disable while debugging - log::info!("Airtable record create needed: {:?}", db_record); - - // Remove it from the map. - airtable_records.remove(&db_record.id); - } - } - } - } - - // AM: Extract out deletions to an explicit sync - - // Iterate over the records remaining and remove them from airtable - // since they don't exist in our vector. - // for (_, record) in records { - // // TODO: Ensure it didn't _just_ get added to the database. - // // Delete the record from airtable. - // record.fields.airtable(db).await?.delete_record(&#new_struct_name::airtable_table(), &record.id).await?; - // } - - Ok(()) - } } }; @@ -604,7 +267,7 @@ fn do_db(attr: TokenStream, item: TokenStream) -> TokenStream { pub airtable_record_id: String, } - #airtable + #db_impl ); new_struct diff --git a/webhooky/src/handlers.rs b/webhooky/src/handlers.rs index 249465c06..ebe1fc5ff 100644 --- a/webhooky/src/handlers.rs +++ b/webhooky/src/handlers.rs @@ -33,7 +33,7 @@ use crate::{ context::ServerContext, handlers_github::RFDUpdater, server::{ - AirtableRowEvent, ApplicationFileUploadData, CounterResponse, GitHubRateLimit, RFDPathParams, + ApplicationFileUploadData, CounterResponse, GitHubRateLimit, RFDPathParams, ShippoTrackingUpdateEvent, }, slack_commands::SlackCommand, @@ -591,366 +591,6 @@ pub async fn handle_slack_interactive( Ok(interactive_response) } -pub async fn handle_airtable_employees_print_home_address_label( - rqctx: &RequestContext, - event: AirtableRowEvent, -) -> Result<()> { - let api_context = rqctx.context(); - - if event.record_id.is_empty() { - bail!("record id is empty"); - } - - // Get the row from airtable. - let user = User::get_from_airtable(&event.record_id, &api_context.app.db, event.cio_company_id).await?; - - // Create a new shipment for the employee and print the label. - user.create_shipment_to_home_address(&api_context.app.db).await?; - - Ok(()) -} - -pub async fn handle_airtable_certificates_renew( - rqctx: &RequestContext, - event: AirtableRowEvent, -) -> Result<()> { - let api_context = rqctx.context(); - - if event.record_id.is_empty() { - bail!("record id is empty"); - } - - // Get the row from airtable. - let mut cert = Certificate::get_from_airtable(&event.record_id, &api_context.app.db, event.cio_company_id).await?; - - let company = cert.company(&api_context.app.db).await?; - let storage = company.cert_storage().await?; - - // Renew the cert. - cert.renew(&api_context.app.db, &company, &storage) - .await - .map_err(|err| { - log::error!("Failed to complete requested renewal for {}", cert.domain); - err - })?; - - Ok(()) -} - -pub async fn handle_airtable_assets_items_print_barcode_label( - rqctx: &RequestContext, - event: AirtableRowEvent, -) -> Result<()> { - let api_context = rqctx.context(); - - if event.record_id.is_empty() { - bail!("record id is empty"); - } - - // Get the row from airtable. - let asset_item = AssetItem::get_from_airtable(&event.record_id, &api_context.app.db, event.cio_company_id).await?; - - // Print the barcode label(s). - asset_item.print_label(&api_context.app.db).await?; - info!("asset item {} printed label", asset_item.name); - - Ok(()) -} - -pub async fn handle_airtable_swag_inventory_items_print_barcode_labels( - rqctx: &RequestContext, - event: AirtableRowEvent, -) -> Result<()> { - let api_context = rqctx.context(); - - if event.record_id.is_empty() { - bail!("record id is empty"); - } - - // Get the row from airtable. - let swag_inventory_item = - SwagInventoryItem::get_from_airtable(&event.record_id, &api_context.app.db, event.cio_company_id).await?; - - // Print the barcode label(s). - swag_inventory_item.print_label(&api_context.app.db).await?; - info!("swag inventory item {} printed label", swag_inventory_item.name); - - Ok(()) -} - -pub async fn handle_airtable_applicants_request_background_check( - rqctx: &RequestContext, - event: AirtableRowEvent, -) -> Result<()> { - let api_context = rqctx.context(); - - if event.record_id.is_empty() { - bail!("record id is empty"); - } - - // Get the row from airtable. - let mut applicant = - Applicant::get_from_airtable(&event.record_id, &api_context.app.db, event.cio_company_id).await?; - if applicant.criminal_background_check_status.is_empty() { - // Request the background check, since we previously have not requested one. - applicant.send_background_check_invitation(&api_context.app.db).await?; - info!("sent background check invitation to applicant: {}", applicant.email); - } - - Ok(()) -} - -pub async fn handle_airtable_applicants_update( - rqctx: &RequestContext, - event: AirtableRowEvent, -) -> Result<()> { - let api_context = rqctx.context(); - - if event.record_id.is_empty() { - bail!("record id is empty"); - } - - // Get the row from airtable. - let applicant = Applicant::get_from_airtable(&event.record_id, &api_context.app.db, event.cio_company_id).await?; - - if applicant.status.is_empty() { - bail!("got an empty applicant status for row: {}", applicant.email); - } - - // Grab our old applicant from the database. - let mut db_applicant = Applicant::get_by_id(&api_context.app.db, applicant.id).await?; - - // Grab the status and the status raw. - let status = cio_api::applicant_status::Status::from_str(&applicant.status).unwrap(); - - let status_changed = db_applicant.status != status.to_string(); - - db_applicant.status = status.to_string(); - if !applicant.raw_status.is_empty() { - // Update the raw status if it had changed. - db_applicant.raw_status = applicant.raw_status.to_string(); - } - - // TODO: should we also update the start date if set in airtable? - // If we do this, we need to update the airtable webhook settings to include it as - // well. - - // If the status is now Giving Offer we should and it's changed from whatever it was before, - // let do the docusign stuff. - if status_changed && status == cio_api::applicant_status::Status::GivingOffer { - // Update the row in our database, first just in case.. - db_applicant.update(&api_context.app.db).await?; - - // Create our docusign client. - let company = db_applicant.company(&api_context.app.db).await?; - let dsa = company.authenticate_docusign(&api_context.app.db).await; - if let Ok(ds) = dsa { - let offer_letter = match applicant.role.as_str() { - "Sales" => api_context - .app - .app_config - .read() - .unwrap() - .envelopes - .create_sales_offer_letter(&db_applicant), - _ => api_context - .app - .app_config - .read() - .unwrap() - .envelopes - .create_offer_letter(&db_applicant), - }; - db_applicant - .do_docusign_offer(&api_context.app.db, &ds, offer_letter) - .await?; - - let piia_letter = api_context - .app - .app_config - .read() - .unwrap() - .envelopes - .create_piia_letter(&db_applicant); - db_applicant - .do_docusign_piia(&api_context.app.db, &ds, piia_letter) - .await?; - } - } - - // Update the row in our database. - db_applicant.update(&api_context.app.db).await?; - - info!("applicant {} updated successfully", applicant.email); - Ok(()) -} - -pub async fn listen_airtable_applicants_recreate_piia_webhooks( - rqctx: &RequestContext, - event: AirtableRowEvent, -) -> Result<()> { - let api_context = rqctx.context(); - - if event.record_id.is_empty() { - bail!("record id is empty"); - } - - // Get the row from airtable. - let applicant = Applicant::get_from_airtable(&event.record_id, &api_context.app.db, event.cio_company_id).await?; - - if applicant.status.is_empty() { - bail!("got an empty applicant status for row: {}", applicant.email); - } - - // Grab our old applicant from the database. - let mut db_applicant = Applicant::get_by_id(&api_context.app.db, applicant.id).await?; - - // Create our docusign client. - let company = db_applicant.company(&api_context.app.db).await?; - let ds = company.authenticate_docusign(&api_context.app.db).await?; - - let piia_letter = api_context - .app - .app_config - .read() - .unwrap() - .envelopes - .create_piia_letter(&db_applicant); - - db_applicant - .send_new_piia_for_accepted_applicant(&api_context.app.db, &ds, piia_letter) - .await?; - - info!("sent applicant {} new PIIA documents successfully", applicant.id); - Ok(()) -} - -pub async fn handle_airtable_shipments_outbound_create( - rqctx: &RequestContext, - event: AirtableRowEvent, -) -> Result<()> { - let api_context = rqctx.context(); - - if event.record_id.is_empty() { - bail!("record id is empty"); - } - - // Get the row from airtable. - let shipment = - OutboundShipment::get_from_airtable(&event.record_id, &api_context.app.db, event.cio_company_id).await?; - - // If it is a row we created from our internal store do nothing. - if shipment.notes.contains("Oxide store") - || shipment.notes.contains("Google sheet") - || shipment.notes.contains("Internal") - || !shipment.provider_id.is_empty() - { - return Ok(()); - } - - if shipment.email.is_empty() { - bail!("got an empty email for row"); - } - - // Update the row in our database. - let mut new_shipment = shipment.update(&api_context.app.db).await?; - // Create the shipment in shippo. - new_shipment.create_or_get_shippo_shipment(&api_context.app.db).await?; - // Update airtable again. - new_shipment.update(&api_context.app.db).await?; - - info!("shipment {} created successfully", shipment.email); - Ok(()) -} - -pub async fn handle_airtable_shipments_outbound_reprint_label( - rqctx: &RequestContext, - event: AirtableRowEvent, -) -> Result<()> { - if event.record_id.is_empty() { - bail!("got an empty email for row"); - } - - let api_context = rqctx.context(); - - // Get the row from airtable. - let mut shipment = - OutboundShipment::get_from_airtable(&event.record_id, &api_context.app.db, event.cio_company_id).await?; - - // Reprint the label. - shipment.print_label(&api_context.app.db).await?; - info!("shipment {} reprinted label", shipment.email); - - // Update the field. - shipment.status = "Label printed".to_string(); - - // Update Airtable. - shipment.update(&api_context.app.db).await?; - - Ok(()) -} - -pub async fn handle_airtable_shipments_outbound_reprint_receipt( - rqctx: &RequestContext, - event: AirtableRowEvent, -) -> Result<()> { - if event.record_id.is_empty() { - bail!("got an empty email for row"); - } - - let api_context = rqctx.context(); - - // Get the row from airtable. - let shipment = - OutboundShipment::get_from_airtable(&event.record_id, &api_context.app.db, event.cio_company_id).await?; - - // Reprint the receipt. - shipment.print_receipt(&api_context.app.db).await?; - info!("shipment {} reprinted receipt", shipment.email); - - // Update Airtable. - shipment.update(&api_context.app.db).await?; - - Ok(()) -} - -pub async fn handle_airtable_shipments_outbound_resend_shipment_status_email_to_recipient( - rqctx: &RequestContext, - event: AirtableRowEvent, -) -> Result<()> { - if event.record_id.is_empty() { - bail!("record id is empty"); - } - - let api_context = rqctx.context(); - - // Get the row from airtable. - let shipment = - OutboundShipment::get_from_airtable(&event.record_id, &api_context.app.db, event.cio_company_id).await?; - - // Resend the email to the recipient. - shipment.send_email_to_recipient(&api_context.app.db).await?; - info!("resent the shipment email to the recipient {}", shipment.email); - - Ok(()) -} - -pub async fn handle_airtable_shipments_outbound_schedule_pickup( - rqctx: &RequestContext, - event: AirtableRowEvent, -) -> Result<()> { - if event.record_id.is_empty() { - bail!("record id is empty"); - } - - // Schedule the pickup. - let api_context = rqctx.context(); - let company = Company::get_by_id(&api_context.app.db, event.cio_company_id).await?; - OutboundShipments::create_pickup(&api_context.app.db, &company).await?; - - Ok(()) -} - pub async fn handle_applicant_review( rqctx: &RequestContext, event: cio_api::applicant_reviews::NewApplicantReview, @@ -981,11 +621,10 @@ pub async fn handle_applicant_review( })?; // Get the applicant for the review. - let mut applicant = Applicant::get_from_airtable( + let mut applicant = Applicant::get_by_airtable_id( + &api_context.app.db, // Get the record id for the applicant. review.applicant.get(0).unwrap(), - &api_context.app.db, - event.cio_company_id, ) .await .map_err(|err| { @@ -1222,40 +861,6 @@ fn get_extension_from_filename(filename: &str) -> Option<&str> { std::path::Path::new(filename).extension().and_then(OsStr::to_str) } -pub async fn handle_airtable_shipments_inbound_create( - rqctx: &RequestContext, - event: AirtableRowEvent, -) -> Result<()> { - if event.record_id.is_empty() { - bail!("record id is empty"); - } - - let api_context = rqctx.context(); - let db = &api_context.app.db; - - // Get the row from airtable. - let record = InboundShipment::get_from_airtable(&event.record_id, db, event.cio_company_id).await?; - - if record.tracking_number.is_empty() || record.carrier.is_empty() { - // Return early, we don't care. - info!("tracking_number and carrier are empty, ignoring"); - return Ok(()); - } - - let mut new_shipment: NewInboundShipment = record.into(); - - new_shipment.expand().await?; - let mut shipment = new_shipment.upsert_in_db(db).await?; - if shipment.airtable_record_id.is_empty() { - shipment.airtable_record_id = event.record_id; - } - shipment.cio_company_id = event.cio_company_id; - shipment.update(db).await?; - - info!("inbound shipment {} updated successfully", shipment.tracking_number); - Ok(()) -} - pub async fn handle_store_order_create(rqctx: &RequestContext, event: Order) -> Result<()> { let api_context = rqctx.context(); @@ -1362,8 +967,6 @@ pub async fn handle_checkr_background_update( .await; if result.is_ok() { let mut applicant = result?; - // Keep the fields from Airtable we need just in case they changed. - applicant.keep_fields_from_airtable(&api_context.app.db).await; // Set the status for the report. if event.data.object.package.contains("premium_criminal") { diff --git a/webhooky/src/handlers_auth.rs b/webhooky/src/handlers_auth.rs index a5421791a..fad300bfe 100644 --- a/webhooky/src/handlers_auth.rs +++ b/webhooky/src/handlers_auth.rs @@ -302,9 +302,8 @@ pub async fn handle_auth_slack_callback( .get_result_async::(api_context.app.db.pool()) .await? } else { - token.create_in_db(&api_context.app.db).await? + token.create(&api_context.app.db).await? }; - new_token.upsert_in_airtable(&api_context.app.db).await?; // Save the user token to the database. if let Some(authed_user) = t.authed_user { @@ -347,9 +346,8 @@ pub async fn handle_auth_slack_callback( .get_result_async::(api_context.app.db.pool()) .await? } else { - user_token.create_in_db(&api_context.app.db).await? + user_token.create(&api_context.app.db).await? }; - new_user_token.upsert_in_airtable(&api_context.app.db).await?; } Ok(()) diff --git a/webhooky/src/handlers_rfd.rs b/webhooky/src/handlers_rfd.rs index 2c5e5965f..b5af7a898 100644 --- a/webhooky/src/handlers_rfd.rs +++ b/webhooky/src/handlers_rfd.rs @@ -114,12 +114,6 @@ pub async fn refresh_db_rfds(context: &Context) -> Result<()> { info!("Updated shorturls for the all rfds"); - // Update rfds in airtable. - RFDs::get_from_db(&context.db, context.company.id) - .await? - .update_airtable(&context.db) - .await?; - Ok(()) } diff --git a/webhooky/src/job.rs b/webhooky/src/job.rs index 41551a82c..12c23e5b0 100644 --- a/webhooky/src/job.rs +++ b/webhooky/src/job.rs @@ -7,14 +7,6 @@ pub async fn run_job_cmd(cmd: crate::core::SubCommand, context: Context) -> Resu let Context { db, company, .. } = context; cio_api::rfd::send_rfd_changelog(&db, &company).await?; } - crate::core::SubCommand::SyncAnalytics(_) => { - let Context { db, company, .. } = context; - cio_api::analytics::refresh_analytics(&db, &company).await?; - } - crate::core::SubCommand::SyncAPITokens(_) => { - let Context { db, company, .. } = context; - cio_api::api_tokens::refresh_api_tokens(&db, &company).await?; - } crate::core::SubCommand::SyncApplications(_) => { let Context { app_config, @@ -26,19 +18,10 @@ pub async fn run_job_cmd(cmd: crate::core::SubCommand, context: Context) -> Resu // Do the new applicants. let app_config = app_config.read().unwrap().clone(); cio_api::applicants::refresh_new_applicants_and_reviews(&db, &company, &app_config).await?; - cio_api::applicant_reviews::refresh_reviews(&db, &company).await?; // Refresh DocuSign for the applicants. cio_api::applicants::refresh_docusign_for_applicants(&db, &company, &app_config).await?; } - crate::core::SubCommand::SyncAssetInventory(_) => { - let Context { db, company, .. } = context; - cio_api::asset_inventory::refresh_asset_items(&db, &company).await?; - } - crate::core::SubCommand::SyncCompanies(_) => { - let Context { db, .. } = context; - cio_api::companies::refresh_companies(&db).await?; - } crate::core::SubCommand::SyncConfigs(_) => { let Context { app_config, @@ -132,40 +115,10 @@ pub async fn run_job_cmd(cmd: crate::core::SubCommand, context: Context) -> Resu let Context { db, company, .. } = context; cio_api::sf::refresh_sf_leads(&db, &company).await?; } - crate::core::SubCommand::SyncShipments(_) => { - let Context { db, company, .. } = context; - let inbound_result = cio_api::shipments::refresh_inbound_shipments(&db, &company).await; - let outbound_result = cio_api::shipments::refresh_outbound_shipments(&db, &company).await; - - if let Err(ref e) = inbound_result { - log::error!("Failed to refresh inbound shipments {:?}", e); - } - - if let Err(ref e) = outbound_result { - log::error!("Failed to refresh outbound shipments {:?}", e); - } - - inbound_result?; - outbound_result?; - } crate::core::SubCommand::SyncShorturls(_) => { let Context { db, company, .. } = context; cio_api::shorturls::refresh_shorturls(&db, &company).await?; } - crate::core::SubCommand::SyncSwagInventory(_) => { - let Context { db, company, .. } = context; - cio_api::swag_inventory::refresh_swag_items(&db, &company).await?; - cio_api::swag_inventory::refresh_swag_inventory_items(&db, &company).await?; - cio_api::swag_inventory::refresh_barcode_scans(&db, &company).await?; - } - crate::core::SubCommand::SyncTravel(_) => { - let Context { db, company, .. } = context; - cio_api::travel::refresh_trip_actions(&db, &company).await?; - } - crate::core::SubCommand::SyncZoho(_) => { - let Context { db, company, .. } = context; - cio_api::zoho::refresh_leads(&db, &company).await?; - } other => anyhow::bail!("Non-job subcommand passed to job runner {:?}", other), } diff --git a/webhooky/src/server.rs b/webhooky/src/server.rs index b2e2bcc87..8a29842da 100644 --- a/webhooky/src/server.rs +++ b/webhooky/src/server.rs @@ -74,29 +74,6 @@ fn create_api() -> ApiDescription { */ api.register(ping).unwrap(); api.register(github_rate_limit).unwrap(); - api.register(listen_airtable_applicants_request_background_check_webhooks) - .unwrap(); - api.register(listen_airtable_applicants_update_webhooks).unwrap(); - api.register(listen_airtable_applicants_recreate_piia_webhooks).unwrap(); - api.register(listen_airtable_assets_items_print_barcode_label_webhooks) - .unwrap(); - api.register(listen_airtable_employees_print_home_address_label_webhooks) - .unwrap(); - api.register(listen_airtable_certificates_renew_webhooks).unwrap(); - api.register(listen_airtable_shipments_inbound_create_webhooks).unwrap(); - api.register(listen_airtable_shipments_outbound_create_webhooks) - .unwrap(); - api.register(listen_airtable_shipments_outbound_reprint_label_webhooks) - .unwrap(); - api.register(listen_airtable_shipments_outbound_reprint_receipt_webhooks) - .unwrap(); - api.register(listen_airtable_shipments_outbound_resend_shipment_status_email_to_recipient_webhooks) - .unwrap(); - api.register(listen_airtable_shipments_outbound_schedule_pickup_webhooks) - .unwrap(); - api.register(listen_airtable_swag_inventory_items_print_barcode_labels_webhooks) - .unwrap(); - api.register(listen_analytics_page_view_webhooks).unwrap(); api.register(listen_application_submit_requests).unwrap(); api.register(listen_test_application_submit_requests).unwrap(); @@ -451,237 +428,6 @@ pub struct GitHubRateLimit { pub reset: String, } -/** - * Listen for a button pressed to print a home address label for employees. - */ -#[endpoint { - method = POST, - path = "/airtable/employees/print_home_address_label", -}] -async fn listen_airtable_employees_print_home_address_label_webhooks( - rqctx: RequestContext, - _auth: Bearer, - body_param: TypedBody, -) -> Result, HttpError> { - crate::handlers::handle_airtable_employees_print_home_address_label(&rqctx, body_param.into_inner()) - .await - .map(accepted) - .map_err(handle_anyhow_err_as_http_err) -} - -/** - * Listen for a button pressed to renew a certificate. - */ -#[endpoint { - method = POST, - path = "/airtable/certificates/renew", -}] -async fn listen_airtable_certificates_renew_webhooks( - rqctx: RequestContext, - _auth: Bearer, - body_param: TypedBody, -) -> Result, HttpError> { - crate::handlers::handle_airtable_certificates_renew(&rqctx, body_param.into_inner()) - .await - .map(accepted) - .map_err(handle_anyhow_err_as_http_err) -} - -/** - * Listen for a button pressed to print a barcode label for an asset item. - */ -#[endpoint { - method = POST, - path = "/airtable/assets/items/print_barcode_label", -}] -async fn listen_airtable_assets_items_print_barcode_label_webhooks( - rqctx: RequestContext, - _auth: Bearer, - body_param: TypedBody, -) -> Result, HttpError> { - crate::handlers::handle_airtable_assets_items_print_barcode_label(&rqctx, body_param.into_inner()) - .await - .map(accepted) - .map_err(handle_anyhow_err_as_http_err) -} - -/** - * Listen for a button pressed to print barcode labels for a swag inventory item. - */ -#[endpoint { - method = POST, - path = "/airtable/swag/inventory/items/print_barcode_labels", -}] -async fn listen_airtable_swag_inventory_items_print_barcode_labels_webhooks( - rqctx: RequestContext, - _auth: Bearer, - body_param: TypedBody, -) -> Result, HttpError> { - crate::handlers::handle_airtable_swag_inventory_items_print_barcode_labels(&rqctx, body_param.into_inner()) - .await - .map(accepted) - .map_err(handle_anyhow_err_as_http_err) -} - -/** - * Listen for a button pressed to request a background check for an applicant. - */ -#[endpoint { - method = POST, - path = "/airtable/applicants/request_background_check", -}] -async fn listen_airtable_applicants_request_background_check_webhooks( - rqctx: RequestContext, - _auth: Bearer, - body_param: TypedBody, -) -> Result, HttpError> { - crate::handlers::handle_airtable_applicants_request_background_check(&rqctx, body_param.into_inner()) - .await - .map(accepted) - .map_err(handle_anyhow_err_as_http_err) -} - -/** - * Listen for rows updated in our Airtable workspace. - * These are set up with an Airtable script on the workspaces themselves. - */ -#[endpoint { - method = POST, - path = "/airtable/applicants/update", -}] -async fn listen_airtable_applicants_update_webhooks( - rqctx: RequestContext, - _auth: Bearer, - body_param: TypedBody, -) -> Result, HttpError> { - crate::handlers::handle_airtable_applicants_update(&rqctx, body_param.into_inner()) - .await - .map(accepted) - .map_err(handle_anyhow_err_as_http_err) -} - -/** - * Listen for requests to recreate and resend PIIA documents for a given applicant - * These are set up with an Airtable script on the workspaces themselves. - */ -#[endpoint { - method = POST, - path = "/airtable/applicants/recreate_piia", -}] -async fn listen_airtable_applicants_recreate_piia_webhooks( - rqctx: RequestContext, - _auth: Bearer, - body_param: TypedBody, -) -> Result, HttpError> { - crate::handlers::listen_airtable_applicants_recreate_piia_webhooks(&rqctx, body_param.into_inner()) - .await - .map(accepted) - .map_err(handle_anyhow_err_as_http_err) -} - -/** - * Listen for rows created in our Airtable workspace. - * These are set up with an Airtable script on the workspaces themselves. - */ -#[endpoint { - method = POST, - path = "/airtable/shipments/outbound/create", -}] -async fn listen_airtable_shipments_outbound_create_webhooks( - rqctx: RequestContext, - _auth: Bearer, - body_param: TypedBody, -) -> Result, HttpError> { - crate::handlers::handle_airtable_shipments_outbound_create(&rqctx, body_param.into_inner()) - .await - .map(accepted) - .map_err(handle_anyhow_err_as_http_err) -} - -/// An Airtable row event. -#[derive(Debug, Clone, Default, JsonSchema, Deserialize, Serialize)] -pub struct AirtableRowEvent { - #[serde(default, skip_serializing_if = "String::is_empty")] - pub record_id: String, - #[serde(default)] - pub cio_company_id: i32, -} - -/** - * Listen for a button pressed to reprint a label for an outbound shipment. - */ -#[endpoint { - method = POST, - path = "/airtable/shipments/outbound/reprint_label", -}] -async fn listen_airtable_shipments_outbound_reprint_label_webhooks( - rqctx: RequestContext, - _auth: Bearer, - body_param: TypedBody, -) -> Result, HttpError> { - crate::handlers::handle_airtable_shipments_outbound_reprint_label(&rqctx, body_param.into_inner()) - .await - .map(accepted) - .map_err(handle_anyhow_err_as_http_err) -} - -/** - * Listen for a button pressed to reprint a receipt for an outbound shipment. - */ -#[endpoint { - method = POST, - path = "/airtable/shipments/outbound/reprint_receipt", -}] -async fn listen_airtable_shipments_outbound_reprint_receipt_webhooks( - rqctx: RequestContext, - _auth: Bearer, - body_param: TypedBody, -) -> Result, HttpError> { - crate::handlers::handle_airtable_shipments_outbound_reprint_receipt(&rqctx, body_param.into_inner()) - .await - .map(accepted) - .map_err(handle_anyhow_err_as_http_err) -} - -/** - * Listen for a button pressed to resend a shipment status email to the recipient for an outbound shipment. - */ -#[endpoint { - method = POST, - path = "/airtable/shipments/outbound/resend_shipment_status_email_to_recipient", -}] -async fn listen_airtable_shipments_outbound_resend_shipment_status_email_to_recipient_webhooks( - rqctx: RequestContext, - _auth: Bearer, - body_param: TypedBody, -) -> Result, HttpError> { - crate::handlers::handle_airtable_shipments_outbound_resend_shipment_status_email_to_recipient( - &rqctx, - body_param.into_inner(), - ) - .await - .map(accepted) - .map_err(handle_anyhow_err_as_http_err) -} - -/** - * Listen for a button pressed to schedule a pickup for an outbound shipment. - */ -#[endpoint { - method = POST, - path = "/airtable/shipments/outbound/schedule_pickup", -}] -async fn listen_airtable_shipments_outbound_schedule_pickup_webhooks( - rqctx: RequestContext, - _auth: Bearer, - body_param: TypedBody, -) -> Result, HttpError> { - crate::handlers::handle_airtable_shipments_outbound_schedule_pickup(&rqctx, body_param.into_inner()) - .await - .map(accepted) - .map_err(handle_anyhow_err_as_http_err) -} - /// A SendGrid incoming email event. #[derive(Debug, Clone, Default, JsonSchema, Deserialize, Serialize)] pub struct IncomingEmail { @@ -1022,25 +768,6 @@ async fn listen_application_files_upload_requests( } } -/** - * Listen for rows created in our Airtable workspace. - * These are set up with an Airtable script on the workspaces themselves. - */ -#[endpoint { - method = POST, - path = "/airtable/shipments/inbound/create", -}] -async fn listen_airtable_shipments_inbound_create_webhooks( - rqctx: RequestContext, - _auth: Bearer, - body_param: TypedBody, -) -> Result, HttpError> { - crate::handlers::handle_airtable_shipments_inbound_create(&rqctx, body_param.into_inner()) - .await - .map(accepted) - .map_err(handle_anyhow_err_as_http_err) -} - /** * Listen for orders being created by the Oxide store. */