From e8a3fe114f51af0755e264270b16b9d67bf21485 Mon Sep 17 00:00:00 2001 From: Nandu Krishna Date: Sat, 15 Nov 2025 19:59:15 +0530 Subject: [PATCH 1/5] add: function to import members --- Cargo.lock | 22 +++++++ Cargo.toml | 1 + src/database_seeder/import_members.rs | 90 +++++++++++++++++++++++++++ 3 files changed, 113 insertions(+) create mode 100644 src/database_seeder/import_members.rs diff --git a/Cargo.lock b/Cargo.lock index 8c0e323..3d61941 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -509,6 +509,27 @@ dependencies = [ "typenum", ] +[[package]] +name = "csv" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52cd9d68cf7efc6ddfaaee42e7288d3a99d613d4b50f76ce9827ae0c6e14f938" +dependencies = [ + "csv-core", + "itoa", + "ryu", + "serde_core", +] + +[[package]] +name = "csv-core" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "704a3c26996a80471189265814dbc2c257598b96b8a7feae2d31ace646bb9782" +dependencies = [ + "memchr", +] + [[package]] name = "darling" version = "0.20.10" @@ -1979,6 +2000,7 @@ dependencies = [ "chrono", "chrono-tz", "config", + "csv", "dotenv", "hex", "hmac", diff --git a/Cargo.toml b/Cargo.toml index ddf26b8..15abea1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,3 +24,4 @@ tracing = "0.1.41" tracing-subscriber = { version = "0.3.20", features = ["env-filter", "time", "fmt", "std"] } dotenv = "0.15.0" time = { version = "0.3.44", features = ["formatting"] } +csv = "1.3" diff --git a/src/database_seeder/import_members.rs b/src/database_seeder/import_members.rs new file mode 100644 index 0000000..71cb266 --- /dev/null +++ b/src/database_seeder/import_members.rs @@ -0,0 +1,90 @@ +use csv::ReaderBuilder; +use std::error::Error; + +pub async fn import_members_from_csv(pool: &sqlx::PgPool, csv_path: &str) -> Result<(), Box> { + let mut reader = ReaderBuilder::new() + .has_headers(true) + .from_path(csv_path)?; + + let mut success_count = 0; + let mut error_count = 0; + + for result in reader.records() { + let record = result?; + + if record.iter().all(|field| field.trim().is_empty()) { + continue; + } + + let roll_no = record.get(0).unwrap_or("").trim(); + let email = record.get(1).unwrap_or("").trim(); + let phone_number = record.get(2).unwrap_or("").trim(); + let github = record.get(3).unwrap_or("").trim(); + let gitlab = record.get(4).unwrap_or("").trim(); + let _discord_username = record.get(5).unwrap_or("").trim(); + let instagram = record.get(6).unwrap_or("").trim(); + let twitter = record.get(7).unwrap_or("").trim(); + let linkedin = record.get(8).unwrap_or("").trim(); + + if roll_no.is_empty() || email.is_empty() { + continue; + } + + let github_user = get_username(github); + let gitlab_user = get_username(gitlab); + + let result = sqlx::query!( + r#" + UPDATE Member + SET + phone_number = $2, + github_user = $3, + gitlab_user = $4, + instagram_handle = $5, + twitter_handle = $6, + linkedin_url = $7 + WHERE LOWER(roll_no) = LOWER($1) + "#, + roll_no, + if phone_number.is_empty() { None } else { Some(phone_number) }, + github_user, + gitlab_user, + if instagram.is_empty() { None } else { Some(instagram) }, + if twitter.is_empty() { None } else { Some(twitter) }, + if linkedin.is_empty() { None } else { Some(linkedin) } + ) + .execute(pool) + .await; + + match result { + Ok(query_result) => { + if query_result.rows_affected() > 0 { + success_count += 1; + println!("Updated member: {}", roll_no); + } else { + println!("Member not found: {}", roll_no); + } + } + Err(e) => { + error_count += 1; + eprintln!("Error updating {}: {}", roll_no, e); + } + } + } + + println!("Successfully updated: {}", success_count); + println!("Errors: {}", error_count); + + Ok(()) +} + +fn get_username(url: &str) -> Option { + if url.is_empty() { + return None; + } + + let parts: Vec<&str> = url.trim_end_matches('/').split('/').collect(); + parts.last() + .filter(|s| !s.is_empty() && **s != "github.com" && **s != "gitlab.com") + .map(|s| s.to_string()) +} From 2dde7c13c6d52f6904ad9f41f3ec15bd2980e839 Mon Sep 17 00:00:00 2001 From: Nandu Krishna Date: Sat, 15 Nov 2025 20:32:06 +0530 Subject: [PATCH 2/5] add: implement helper function --- src/bin/import_csv.rs | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 src/bin/import_csv.rs diff --git a/src/bin/import_csv.rs b/src/bin/import_csv.rs new file mode 100644 index 0000000..e87c8c1 --- /dev/null +++ b/src/bin/import_csv.rs @@ -0,0 +1,31 @@ +use sqlx::PgPool; +use std::env; + +include!("../database_seeder/import_members.rs"); + +#[tokio::main] +async fn main() { + let _ = dotenv::dotenv(); + + let database_url = env::var("ROOT_DB_URL").expect("ROOT_DB_URL must be set"); + let csv_path = env::args() + .nth(1) + .unwrap_or_else(|| "Member List.csv".to_string()); + + println!("Starting CSV import from: {}", csv_path); + println!("Connecting to database..."); + + let pool = PgPool::connect(&database_url) + .await + .expect("Failed to connect to database"); + + println!("Connected to database"); + println!("Importing members...\n"); + + match import_members_from_csv(&pool, &csv_path).await { + Ok(_) => println!("\nImport completed successfully!"), + Err(e) => eprintln!("\nImport failed: {}", e), + } + + pool.close().await; +} From 2febd931f7955fd6012fdf9831c48f5251dc3628 Mon Sep 17 00:00:00 2001 From: Nandu Krishna Date: Sat, 15 Nov 2025 20:48:02 +0530 Subject: [PATCH 3/5] add: migration for social media links --- migrations/20251114124741_add_social_media.sql | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 migrations/20251114124741_add_social_media.sql diff --git a/migrations/20251114124741_add_social_media.sql b/migrations/20251114124741_add_social_media.sql new file mode 100644 index 0000000..7716344 --- /dev/null +++ b/migrations/20251114124741_add_social_media.sql @@ -0,0 +1,8 @@ +-- Add migration script here +-- Add social media and contact columns to Member table +ALTER TABLE Member +ADD COLUMN phone_number VARCHAR(20), +ADD COLUMN gitlab_user VARCHAR(255), +ADD COLUMN instagram_handle VARCHAR(255), +ADD COLUMN twitter_handle VARCHAR(255), +ADD COLUMN linkedin_url TEXT; From f470ed0205b3e4f21f4cc51fe837dee1ae99e834 Mon Sep 17 00:00:00 2001 From: Nandu Krishna Date: Sat, 15 Nov 2025 20:51:34 +0530 Subject: [PATCH 4/5] add: member social links to models --- src/bin/import_csv.rs | 2 +- src/models/member.rs | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/bin/import_csv.rs b/src/bin/import_csv.rs index e87c8c1..c9e1100 100644 --- a/src/bin/import_csv.rs +++ b/src/bin/import_csv.rs @@ -6,7 +6,7 @@ include!("../database_seeder/import_members.rs"); #[tokio::main] async fn main() { let _ = dotenv::dotenv(); - + let database_url = env::var("ROOT_DB_URL").expect("ROOT_DB_URL must be set"); let csv_path = env::args() .nth(1) diff --git a/src/models/member.rs b/src/models/member.rs index 4fdfed5..4885d1f 100644 --- a/src/models/member.rs +++ b/src/models/member.rs @@ -25,6 +25,11 @@ pub struct Member { pub group_id: i32, pub track: Option, pub github_user: Option, + pub phone_number: Option, + pub gitlab_user: Option, + pub instagram_handle: Option, + pub twitter_handle: Option, + pub linkedin_url: Option, pub created_at: NaiveDateTime, } From c8663159bd24d3fd35e835d5d2d1ec611297e8bc Mon Sep 17 00:00:00 2001 From: Nandu Krishna Date: Sat, 15 Nov 2025 21:34:11 +0530 Subject: [PATCH 5/5] docs: update seeding --- docs/docs.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/docs.md b/docs/docs.md index 3fc5196..cc50e62 100644 --- a/docs/docs.md +++ b/docs/docs.md @@ -26,6 +26,11 @@ Database seeding can be enabled by setting `SEEDING_ENABLED` to `true` in the `. # Seed toggle SEEDING_ENABLED=true ``` +If CSV file for member list is available, you can import it using +``` +cargo run --bin import_csv "Member List.csv" +``` +You may need to create appropriate columns if it doesn't exist. ## Core Features ### Member Management