Spaces:
Sleeping
Sleeping
| import pymysql | |
| import json | |
| import os | |
| import time | |
| from dotenv import load_dotenv | |
| from werkzeug.security import generate_password_hash, check_password_hash | |
| load_dotenv(override=True) | |
| DB_HOST = os.environ.get("DB_HOST", "localhost") | |
| DB_USER = os.environ.get("DB_USER", "root") | |
| DB_PASS = os.environ.get("DB_PASS", "") | |
| DB_NAME = os.environ.get("DB_NAME", "vorniity") | |
| def get_db_connection(): | |
| return pymysql.connect( | |
| host=DB_HOST, | |
| user=DB_USER, | |
| password=DB_PASS, | |
| database=DB_NAME, | |
| cursorclass=pymysql.cursors.DictCursor | |
| ) | |
| def init_db(): | |
| """Initialize the MySQL database for caching results.""" | |
| try: | |
| conn = get_db_connection() | |
| with conn.cursor() as cursor: | |
| cursor.execute(''' | |
| CREATE TABLE IF NOT EXISTS results_cache_v2 ( | |
| usn VARCHAR(20), | |
| url VARCHAR(255), | |
| data JSON, | |
| timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, | |
| PRIMARY KEY (usn, url) | |
| ) | |
| ''') | |
| cursor.execute(''' | |
| CREATE TABLE IF NOT EXISTS subject_credits ( | |
| subject_code VARCHAR(50) PRIMARY KEY, | |
| credits INT NOT NULL | |
| ) | |
| ''') | |
| cursor.execute(''' | |
| CREATE TABLE IF NOT EXISTS classes ( | |
| id INT AUTO_INCREMENT PRIMARY KEY, | |
| name VARCHAR(255) NOT NULL, | |
| start_usn VARCHAR(20) NULL, | |
| end_usn VARCHAR(20) NULL, | |
| lateral_start VARCHAR(20) NULL, | |
| lateral_end VARCHAR(20) NULL, | |
| usn_list MEDIUMTEXT NULL | |
| ) | |
| ''') | |
| try: | |
| cursor.execute("ALTER TABLE classes ADD COLUMN lateral_start VARCHAR(20) NULL AFTER end_usn") | |
| cursor.execute("ALTER TABLE classes ADD COLUMN lateral_end VARCHAR(20) NULL AFTER lateral_start") | |
| except Exception: | |
| pass | |
| cursor.execute(''' | |
| CREATE TABLE IF NOT EXISTS scrape_history ( | |
| id VARCHAR(255) PRIMARY KEY, | |
| start_usn VARCHAR(20), | |
| end_usn VARCHAR(20), | |
| total_usns INT, | |
| completed INT, | |
| time_taken FLOAT, | |
| status VARCHAR(50), | |
| timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP | |
| ) | |
| ''') | |
| cursor.execute(''' | |
| CREATE TABLE IF NOT EXISTS users ( | |
| id INT AUTO_INCREMENT PRIMARY KEY, | |
| name VARCHAR(255) NOT NULL, | |
| college VARCHAR(255) NOT NULL, | |
| email VARCHAR(255) UNIQUE NOT NULL, | |
| phone VARCHAR(50), | |
| password_hash VARCHAR(255) NOT NULL, | |
| created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP | |
| ) | |
| ''') | |
| conn.commit() | |
| conn.close() | |
| except Exception as e: | |
| print(f"[DB ERROR] Failed to initialize DB: {e}") | |
| def seed_credits_if_empty(hardcoded_map): | |
| """Ensure hardcoded subject credits exist in the DB without overwriting user changes.""" | |
| try: | |
| conn = get_db_connection() | |
| with conn.cursor() as cursor: | |
| for code, credit in hardcoded_map.items(): | |
| cursor.execute( | |
| """ | |
| INSERT INTO subject_credits (subject_code, credits) | |
| VALUES (%s, %s) | |
| ON DUPLICATE KEY UPDATE credits = IF(credits = 0, VALUES(credits), credits) | |
| """, | |
| (code, credit) | |
| ) | |
| conn.commit() | |
| conn.close() | |
| except Exception as e: | |
| print(f"[DB ERROR] Failed to seed credits: {e}") | |
| _CREDITS_CACHE = {} | |
| _CREDITS_LAST_FETCH = 0 | |
| _CACHE_TTL = 300 # 5 minutes cache to prevent Hostinger max_connections_per_hour (500) errors | |
| def get_all_credits(force_refresh=False): | |
| """Retrieve all subject credits as a dictionary with caching.""" | |
| global _CREDITS_CACHE, _CREDITS_LAST_FETCH | |
| current_time = time.time() | |
| if not force_refresh and _CREDITS_CACHE and (current_time - _CREDITS_LAST_FETCH < _CACHE_TTL): | |
| return _CREDITS_CACHE | |
| try: | |
| conn = get_db_connection() | |
| with conn.cursor() as cursor: | |
| cursor.execute("SELECT subject_code, credits FROM subject_credits") | |
| rows = cursor.fetchall() | |
| conn.close() | |
| _CREDITS_CACHE = {row['subject_code']: row['credits'] for row in rows} | |
| _CREDITS_LAST_FETCH = current_time | |
| return _CREDITS_CACHE | |
| except Exception as e: | |
| print(f"[DB ERROR] Failed to fetch credits: {e}") | |
| return _CREDITS_CACHE if _CREDITS_CACHE else {} | |
| def save_credit(subject_code, credits): | |
| """Save or update a subject credit.""" | |
| try: | |
| conn = get_db_connection() | |
| with conn.cursor() as cursor: | |
| cursor.execute( | |
| "REPLACE INTO subject_credits (subject_code, credits) VALUES (%s, %s)", | |
| (subject_code.upper(), int(credits)) | |
| ) | |
| conn.commit() | |
| conn.close() | |
| # Instantly update cache | |
| global _CREDITS_CACHE | |
| if _CREDITS_CACHE is not None: | |
| _CREDITS_CACHE[subject_code.upper()] = int(credits) | |
| return True | |
| except Exception as e: | |
| print(f"[DB ERROR] Failed to save credit: {e}") | |
| return False | |
| def save_bulk_credits(credits_list): | |
| """Save multiple subject credits efficiently.""" | |
| try: | |
| conn = get_db_connection() | |
| with conn.cursor() as cursor: | |
| # credits_list is a list of dicts: [{'subject_code': 'ABC', 'credits': 3}, ...] | |
| values = [(item['subject_code'].upper(), int(item['credits'])) for item in credits_list] | |
| cursor.executemany( | |
| "REPLACE INTO subject_credits (subject_code, credits) VALUES (%s, %s)", | |
| values | |
| ) | |
| conn.commit() | |
| conn.close() | |
| # Invalidate cache so next fetch gets everything | |
| global _CREDITS_CACHE | |
| _CREDITS_CACHE = None | |
| return True | |
| except Exception as e: | |
| print(f"[DB ERROR] Failed to save bulk credits: {e}") | |
| return False | |
| def delete_credit(subject_code): | |
| """Delete a subject credit.""" | |
| try: | |
| conn = get_db_connection() | |
| with conn.cursor() as cursor: | |
| cursor.execute("DELETE FROM subject_credits WHERE subject_code = %s", (subject_code.upper(),)) | |
| conn.commit() | |
| conn.close() | |
| return True | |
| except Exception as e: | |
| print(f"[DB ERROR] Failed to delete credit: {e}") | |
| return False | |
| # --- Classes Logic --- | |
| def create_class(name, start_usn=None, end_usn=None, lateral_start=None, lateral_end=None, usn_list=None): | |
| try: | |
| conn = get_db_connection() | |
| with conn.cursor() as cursor: | |
| cursor.execute( | |
| "INSERT INTO classes (name, start_usn, end_usn, lateral_start, lateral_end, usn_list) VALUES (%s, %s, %s, %s, %s, %s)", | |
| (name, start_usn.upper() if start_usn else None, end_usn.upper() if end_usn else None, lateral_start.upper() if lateral_start else None, lateral_end.upper() if lateral_end else None, usn_list) | |
| ) | |
| conn.commit() | |
| conn.close() | |
| return True | |
| except Exception as e: | |
| print(f"[DB ERROR] Failed to create class: {e}") | |
| return False | |
| def get_all_classes(): | |
| try: | |
| conn = get_db_connection() | |
| with conn.cursor() as cursor: | |
| cursor.execute("SELECT id, name, start_usn, end_usn, lateral_start, lateral_end, usn_list FROM classes ORDER BY id DESC") | |
| rows = cursor.fetchall() | |
| conn.close() | |
| return rows | |
| except Exception as e: | |
| print(f"[DB ERROR] Failed to fetch classes: {e}") | |
| return [] | |
| def delete_class(class_id): | |
| try: | |
| conn = get_db_connection() | |
| with conn.cursor() as cursor: | |
| cursor.execute("DELETE FROM classes WHERE id = %s", (class_id,)) | |
| conn.commit() | |
| conn.close() | |
| return True | |
| except Exception as e: | |
| print(f"[DB ERROR] Failed to delete class: {e}") | |
| return False | |
| def update_class(class_id, name, start_usn=None, end_usn=None, lateral_start=None, lateral_end=None, usn_list=None): | |
| try: | |
| conn = get_db_connection() | |
| with conn.cursor() as cursor: | |
| cursor.execute( | |
| "UPDATE classes SET name = %s, start_usn = %s, end_usn = %s, lateral_start = %s, lateral_end = %s, usn_list = %s WHERE id = %s", | |
| (name, start_usn.upper() if start_usn else None, end_usn.upper() if end_usn else None, lateral_start.upper() if lateral_start else None, lateral_end.upper() if lateral_end else None, usn_list, class_id) | |
| ) | |
| conn.commit() | |
| conn.close() | |
| return True | |
| except Exception as e: | |
| print(f"[DB ERROR] Failed to update class: {e}") | |
| return False | |
| # --- Scrape History Logic --- | |
| def save_scrape_history(job_id, start_usn, end_usn, total_usns, completed, time_taken, status): | |
| try: | |
| conn = get_db_connection() | |
| with conn.cursor() as cursor: | |
| cursor.execute( | |
| "REPLACE INTO scrape_history (id, start_usn, end_usn, total_usns, completed, time_taken, status) VALUES (%s, %s, %s, %s, %s, %s, %s)", | |
| (job_id, start_usn, end_usn, total_usns, completed, time_taken, status) | |
| ) | |
| conn.commit() | |
| conn.close() | |
| return True | |
| except Exception as e: | |
| print(f"[DB ERROR] Failed to save scrape history: {e}") | |
| return False | |
| def get_scrape_history(): | |
| try: | |
| conn = get_db_connection() | |
| with conn.cursor() as cursor: | |
| cursor.execute("SELECT id, start_usn, end_usn, total_usns, completed, time_taken, status, timestamp FROM scrape_history ORDER BY timestamp DESC") | |
| rows = cursor.fetchall() | |
| conn.close() | |
| # Format timestamp to ISO string | |
| for row in rows: | |
| if row['timestamp']: | |
| row['timestamp'] = row['timestamp'].isoformat() | |
| return rows | |
| except Exception as e: | |
| print(f"[DB ERROR] Failed to fetch scrape history: {e}") | |
| return [] | |
| def delete_student(usn): | |
| try: | |
| conn = get_db_connection() | |
| with conn.cursor() as cursor: | |
| cursor.execute("DELETE FROM results_cache_v2 WHERE usn = %s", (usn.upper(),)) | |
| conn.commit() | |
| conn.close() | |
| return True | |
| except Exception as e: | |
| print(f"[DB ERROR] Failed to delete student: {e}") | |
| return False | |
| def get_cached_result(usn, url): | |
| """Retrieve a cached result for a given USN and URL if it exists.""" | |
| try: | |
| conn = get_db_connection() | |
| with conn.cursor() as cursor: | |
| cursor.execute("SELECT data FROM results_cache_v2 WHERE usn = %s AND url = %s", (usn.upper(), url)) | |
| row = cursor.fetchone() | |
| conn.close() | |
| if row: | |
| if isinstance(row['data'], str): | |
| return json.loads(row['data']) | |
| return row['data'] | |
| except Exception as e: | |
| print(f"[CACHE ERROR] Failed to read cache for {usn}: {e}") | |
| return None | |
| def save_cached_result(usn, url, result_dict): | |
| """Save a successfully parsed result to the cache.""" | |
| try: | |
| conn = get_db_connection() | |
| with conn.cursor() as cursor: | |
| # REPLACE INTO handles updating the record if it already exists | |
| cursor.execute( | |
| "REPLACE INTO results_cache_v2 (usn, url, data) VALUES (%s, %s, %s)", | |
| (usn.upper(), url, json.dumps(result_dict)) | |
| ) | |
| conn.commit() | |
| conn.close() | |
| except Exception as e: | |
| print(f"[CACHE ERROR] Failed to save cache for {usn}: {e}") | |
| def clear_database(): | |
| """Wipe all student scraped data, results, and history.""" | |
| try: | |
| conn = get_db_connection() | |
| with conn.cursor() as cursor: | |
| cursor.execute("SET FOREIGN_KEY_CHECKS = 0;") | |
| cursor.execute("TRUNCATE TABLE results_cache_v2;") | |
| cursor.execute("TRUNCATE TABLE scrape_history;") | |
| cursor.execute("SET FOREIGN_KEY_CHECKS = 1;") | |
| conn.commit() | |
| conn.close() | |
| return True, "Database cleared successfully" | |
| except Exception as e: | |
| print(f"[DB ERROR] Failed to clear database: {e}") | |
| return False, str(e) | |
| # --- Auth Logic --- | |
| def create_user(name, college, email, phone, password): | |
| try: | |
| conn = get_db_connection() | |
| password_hash = generate_password_hash(password) | |
| with conn.cursor() as cursor: | |
| cursor.execute( | |
| "INSERT INTO users (name, college, email, phone, password_hash) VALUES (%s, %s, %s, %s, %s)", | |
| (name, college, email, phone, password_hash) | |
| ) | |
| conn.commit() | |
| conn.close() | |
| return True, "User created successfully" | |
| except pymysql.err.IntegrityError: | |
| return False, "Email already exists" | |
| except Exception as e: | |
| print(f"[DB ERROR] Failed to create user: {e}") | |
| return False, str(e) | |
| def get_user_by_email(email): | |
| try: | |
| conn = get_db_connection() | |
| with conn.cursor() as cursor: | |
| cursor.execute("SELECT * FROM users WHERE email = %s", (email,)) | |
| user = cursor.fetchone() | |
| conn.close() | |
| return user | |
| except Exception as e: | |
| print(f"[DB ERROR] Failed to get user: {e}") | |
| return None | |