From be5e8db579257ef7d3e4adc693b977e5e1ab788d Mon Sep 17 00:00:00 2001 From: Cipher Vance Date: Mon, 25 Aug 2025 13:02:38 -0500 Subject: [PATCH] feat: optimize TTFB with lazy DB init and newsletter caching --- server.py | 142 ++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 105 insertions(+), 37 deletions(-) diff --git a/server.py b/server.py index 3de5e29..47d4f30 100644 --- a/server.py +++ b/server.py @@ -15,7 +15,92 @@ SMTP_USER = os.getenv('SMTP_USER') SMTP_PASSWORD = os.getenv('SMTP_PASSWORD') app = Flask(__name__) -init_db() + +_db_initialized = False +_newsletter_cache = {} +_cache_timestamp = {} +CACHE_DURATION = 300 + +def ensure_db_initialized(): + """Lazy database initialization - only runs on first database access""" + global _db_initialized + if not _db_initialized: + init_db() + _db_initialized = True + +def get_newsletters_cached(): + """Get newsletters with caching to reduce database hits""" + current_time = time.time() + + if ('newsletters' in _newsletter_cache and + current_time - _cache_timestamp.get('newsletters', 0) < CACHE_DURATION): + return _newsletter_cache['newsletters'] + + ensure_db_initialized() + conn = get_connection() + cursor = conn.cursor() + cursor.execute( + "SELECT id, subject, body, sent_at " + "FROM newsletters ORDER BY sent_at DESC" + ) + rows = cursor.fetchall() + cursor.close() + conn.close() + + newsletters = [ + {"id": r[0], "subject": r[1], "body": r[2], "sent_at": r[3]} + for r in rows + ] + + _newsletter_cache['newsletters'] = newsletters + _cache_timestamp['newsletters'] = current_time + + return newsletters + +def get_newsletter_by_id_cached(newsletter_id): + """Get single newsletter with caching""" + cache_key = f'newsletter_{newsletter_id}' + current_time = time.time() + + if (cache_key in _newsletter_cache and + current_time - _cache_timestamp.get(cache_key, 0) < CACHE_DURATION): + return _newsletter_cache[cache_key] + + ensure_db_initialized() + conn = get_connection() + cursor = conn.cursor() + cursor.execute( + "SELECT id, subject, body, sent_at " + "FROM newsletters WHERE id = %s", + (newsletter_id,) + ) + row = cursor.fetchone() + cursor.close() + conn.close() + + if not row: + return None + + newsletter = { + "id": row[0], + "subject": row[1], + "body": row[2], + "sent_at": row[3] + } + + _newsletter_cache[cache_key] = newsletter + _cache_timestamp[cache_key] = current_time + + return newsletter + +def clear_newsletter_cache(): + """Clear newsletter cache when data is updated""" + global _newsletter_cache, _cache_timestamp + keys_to_remove = [k for k in _newsletter_cache.keys() + if k.startswith('newsletter')] + for key in keys_to_remove: + _newsletter_cache.pop(key, None) + _cache_timestamp.pop(key, None) @app.before_request def start_timer(): @@ -58,15 +143,19 @@ def send_confirmation_async(email, unsubscribe_link): @app.route("/", methods=["GET"]) def index(): + """Home page - no database access needed""" return render_template("index.html") @app.route("/subscribe", methods=["POST"]) def subscribe(): + """Subscribe endpoint - lazy loads database only when needed""" data = request.get_json() or {} email = data.get("email") if not email: return jsonify(error="No email provided"), 400 + ensure_db_initialized() + if add_email(email): unsubscribe_link = f"{request.url_root}unsubscribe?email={email}" @@ -82,63 +171,42 @@ def subscribe(): @app.route("/unsubscribe", methods=["GET"]) def unsubscribe(): + """Unsubscribe endpoint - lazy loads database only when needed""" email = request.args.get("email") if not email: return "No email specified.", 400 + ensure_db_initialized() + if remove_email(email): return f"The email {email} has been unsubscribed.", 200 return f"Email {email} was not found or has already been unsubscribed.", 400 - @app.route("/newsletters", methods=["GET"]) def newsletters(): """ - List all newsletters (newest first). + List all newsletters (newest first) with caching for better performance. """ - conn = get_connection() - cursor = conn.cursor() - cursor.execute( - "SELECT id, subject, body, sent_at " - + "FROM newsletters ORDER BY sent_at DESC" - ) - rows = cursor.fetchall() - cursor.close() - conn.close() - - newsletters = [ - {"id": r[0], "subject": r[1], "body": r[2], "sent_at": r[3]} - for r in rows - ] + newsletters = get_newsletters_cached() return render_template("newsletters.html", newsletters=newsletters) - @app.route("/newsletter/", methods=["GET"]) def newsletter_detail(newsletter_id): """ - Show a single newsletter by its ID. + Show a single newsletter by its ID with caching. """ - conn = get_connection() - cursor = conn.cursor() - cursor.execute( - "SELECT id, subject, body, sent_at " - + "FROM newsletters WHERE id = %s", - (newsletter_id,) - ) - row = cursor.fetchone() - cursor.close() - conn.close() - - if not row: + newsletter = get_newsletter_by_id_cached(newsletter_id) + + if not newsletter: return "Newsletter not found.", 404 - newsletter = { - "id": row[0], - "subject": row[1], - "body": row[2], - "sent_at": row[3] - } return render_template("newsletter_detail.html", newsletter=newsletter) +@app.route("/admin/clear-cache", methods=["POST"]) +def clear_cache(): + """Admin endpoint to clear newsletter cache""" + clear_newsletter_cache() + return jsonify(message="Cache cleared successfully"), 200 + if __name__ == "__main__": app.run(host="0.0.0.0", debug=True) \ No newline at end of file