From 7f14e0f3bde47d4b530ae147303380ffba4209a7 Mon Sep 17 00:00:00 2001 From: Vishnu Tejas Date: Sat, 3 May 2025 22:58:40 +0530 Subject: [PATCH] Implemented database seeding as per #61 --- .env.sample | 3 + src/database_seeder/mod.rs | 20 ++++++ src/database_seeder/seed.sql | 125 +++++++++++++++++++++++++++++++++++ src/main.rs | 11 +++ 4 files changed, 159 insertions(+) create mode 100644 src/database_seeder/mod.rs create mode 100644 src/database_seeder/seed.sql diff --git a/.env.sample b/.env.sample index 34d98ac..f1d6717 100644 --- a/.env.sample +++ b/.env.sample @@ -9,3 +9,6 @@ DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST} RUST_ENV=development ROOT_SECRET=insecuresecret123 # Used to verify origin of attendance mutations ROOT_PORT=3000 + +# Seed toggle +SEEDING_ENABLED=false \ No newline at end of file diff --git a/src/database_seeder/mod.rs b/src/database_seeder/mod.rs new file mode 100644 index 0000000..0454a98 --- /dev/null +++ b/src/database_seeder/mod.rs @@ -0,0 +1,20 @@ +use sqlx::PgPool; +use std::fs; +use std::path::Path; + +pub async fn seed_database(pool: &PgPool) { + let sql_path = Path::new("src/database_seeder/seed.sql"); + let seed_sql = fs::read_to_string(sql_path).expect("Failed to read seed.sql file"); + + let statements: Vec<&str> = seed_sql + .split(';') + .filter(|stmt| !stmt.trim().is_empty()) + .collect(); + + for statement in statements { + sqlx::query(statement) + .execute(pool) + .await + .expect("Failed to execute seed statement"); + } +} diff --git a/src/database_seeder/seed.sql b/src/database_seeder/seed.sql new file mode 100644 index 0000000..6b8930f --- /dev/null +++ b/src/database_seeder/seed.sql @@ -0,0 +1,125 @@ +-- Member +INSERT INTO member ( + roll_no, name, email, sex, year, hostel, mac_address, discord_id, group_id +) +SELECT + 'R' || LPAD(i::TEXT, 4, '0'), + CASE + WHEN i % 5 = 0 THEN 'John Doe ' || i + WHEN i % 5 = 1 THEN 'Jane Smith ' || i + WHEN i % 5 = 2 THEN 'Alex Johnson ' || i + WHEN i % 5 = 3 THEN 'Emily Davis ' || i + ELSE 'Chris Brown ' || i + END, + CASE + WHEN i % 5 = 0 THEN 'john.doe' || i || '@example.com' + WHEN i % 5 = 1 THEN 'jane.smith' || i || '@example.com' + WHEN i % 5 = 2 THEN 'alex.johnson' || i || '@example.com' + WHEN i % 5 = 3 THEN 'emily.davis' || i || '@example.com' + ELSE 'chris.brown' || i || '@example.com' + END, + CASE + WHEN i % 2 = 0 THEN 'M'::sex_type + ELSE 'F'::sex_type + END, + (i % 4) + 1, + 'Hostel ' || ((i % 5) + 1), + '00:14:22:01:' || LPAD(TO_HEX(i), 2, '0') || ':' || LPAD(TO_HEX(i + 60), 2, '0'), + 'discord_user_' || i, + (i % 8) + 1 +FROM generate_series(1, 60) AS i +ON CONFLICT (roll_no) DO NOTHING; + + +-- Attendance +INSERT INTO Attendance ( + member_id, date, is_present, time_in, time_out +) +SELECT + m.member_id, + CURRENT_DATE - ((i * 3) % 30), + rnd.is_present, + CASE WHEN rnd.is_present THEN rnd.time_in ELSE NULL END, + CASE WHEN rnd.is_present THEN rnd.time_out ELSE NULL END +FROM generate_series(1, 600) AS i +JOIN ( + SELECT generate_series(1, 60) AS idx, member_id + FROM member +) AS m ON (i % 60) + 1 = m.idx +JOIN ( + SELECT + TRUE AS is_present, + '08:30'::TIME + (INTERVAL '1 minute' * (random() * 60)) AS time_in, + '17:00'::TIME + (INTERVAL '1 minute' * (random() * 60)) AS time_out + UNION ALL + SELECT FALSE, NULL, NULL +) AS rnd ON TRUE +WHERE (random() < 0.75) +ON CONFLICT (member_id, date) DO NOTHING; + + +-- AttendanceSummary +INSERT INTO AttendanceSummary ( + member_id, year, month, days_attended +) +SELECT + m.member_id, + 2025, + (i % 12) + 1, + FLOOR(random() * 26 + 3)::INT +FROM generate_series(1, 400) AS i +JOIN ( + SELECT generate_series(1, 60) AS idx, member_id + FROM member +) AS m ON (i % 60) + 1 = m.idx +ON CONFLICT (member_id, year, month) DO NOTHING; + + +-- StatusUpdateStreak +INSERT INTO StatusUpdateStreak ( + member_id, current_streak, max_streak +) +SELECT + member_id, + FLOOR(random() * 10 + 1)::INT, + FLOOR(random() * 30 + 10)::INT +FROM member +ON CONFLICT (member_id) DO NOTHING; + + +-- Project +INSERT INTO Project ( + member_id, title +) +SELECT + (i % 60) + 1, + CASE + WHEN i % 3 = 0 THEN 'Machine Learning Project ' || i + WHEN i % 3 = 1 THEN 'Web Development Project ' || i + ELSE 'Data Analysis Project ' || i + END +FROM generate_series(1, 200) AS i +WHERE NOT EXISTS ( + SELECT 1 FROM Project + WHERE member_id = (i % 60) + 1 AND title = CASE + WHEN i % 3 = 0 THEN 'Machine Learning Project ' || i + WHEN i % 3 = 1 THEN 'Web Development Project ' || i + ELSE 'Data Analysis Project ' || i + END +); + + +-- StatusUpdateHistory +INSERT INTO StatusUpdateHistory ( + member_id, date, is_updated +) +SELECT + m.member_id, + CURRENT_DATE - ((i * 2) % 30), + i % 2 = 0 +FROM generate_series(1, 500) AS i +JOIN ( + SELECT generate_series(1, 60) AS idx, member_id + FROM member +) AS m ON (i % 60) + 1 = m.idx +ON CONFLICT (member_id, date) DO NOTHING; diff --git a/src/main.rs b/src/main.rs index 0536d54..7ba9873 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,10 +8,12 @@ use tracing::info; use tracing_subscriber::{fmt, layer::SubscriberExt, util::SubscriberInitExt, EnvFilter}; use daily_task::run_daily_task_at_midnight; +use database_seeder::seed_database; use graphql::{Mutation, Query}; use routes::setup_router; pub mod daily_task; +pub mod database_seeder; pub mod graphql; pub mod models; pub mod routes; @@ -23,6 +25,7 @@ struct Config { secret_key: String, database_url: String, port: String, + seeding_enabled: bool, } impl Config { @@ -33,6 +36,9 @@ impl Config { secret_key: std::env::var("ROOT_SECRET").expect("ROOT_SECRET must be set."), database_url: std::env::var("DATABASE_URL").expect("DATABASE_URL must be set."), port: std::env::var("ROOT_PORT").expect("ROOT_PORT must be set."), + seeding_enabled: std::env::var("SEEDING_ENABLED") + .map(|v| v.to_lowercase() == "true") + .unwrap_or(false), } } } @@ -45,6 +51,11 @@ async fn main() { let pool = setup_database(&config.database_url).await; let schema = build_graphql_schema(pool.clone(), config.secret_key); + if config.seeding_enabled { + info!("Seeding database..."); + seed_database(&pool).await; + } + tokio::task::spawn(async { run_daily_task_at_midnight(pool).await; });