From b9a8e2591c7537af09c45e03b1bde259f1e03724 Mon Sep 17 00:00:00 2001 From: bender Date: Wed, 10 Jun 2026 20:11:59 +0000 Subject: [PATCH] Add app.py --- app.py | 96 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 app.py diff --git a/app.py b/app.py new file mode 100644 index 0000000..0a6c308 --- /dev/null +++ b/app.py @@ -0,0 +1,96 @@ +from flask import Flask, jsonify +from flask_cors import CORS +from scraper import scrape_bokadirekt_appointments +import datetime +import threading +import os + +app = Flask(__name__) +CORS(app) # Enable CORS for all origins; adjust for production + +# --- Configuration --- +# Salon URLs to scrape from Bokadirekt. These should be actual salon profile pages. +# Replace with real URLs as identified from the frontend 'faq' section. +SALON_URLS = [ + "https://www.bokadirekt.se/places/klipphuset-41113", # Add other Bokadirekt salon URLs in Kungsbacka here: + # e.g., "https://www.bokadirekt.se/places/klippa-kungsbacka-XXXXX", # "https://www.bokadirekt.se/places/studio-siss-XXXXX", # "https://www.bokadirekt.se/places/det-hander-XXXXX", # "https://www.bokadirekt.se/places/by-u.s.-XXXXX"] + +# Cache for scraped data +cached_appointments = [] +last_scraped_time = None +SCRAPE_INTERVAL_MINUTES = 30 # As mentioned in the frontend description + +# --- Scraper Functions --- +# This function runs in a background thread to update the cache periodically. +def update_cached_appointments(): + global cached_appointments, last_scraped_time + print(f"[{datetime.datetime.now()}] Starting periodic scrape...") + try: + scraped_data = scrape_bokadirekt_appointments(SALON_URLS) + if scraped_data: + cached_appointments = scraped_data + last_scraped_time = datetime.datetime.now() + print(f"[{datetime.datetime.now()}] Scrape successful. {len(cached_appointments)} appointments cached.") + else: + print(f"[{datetime.datetime.now()}] Scraper returned no data. Keeping previous cache if any.") + except Exception as e: + print(f"[{datetime.datetime.now()}] Error during periodic scrape: {e}") + + # Schedule the next scrape + threading.Timer(SCRAPE_INTERVAL_MINUTES * 60, update_cached_appointments).start() + +# Synchronous update for initial load or immediate refresh if needed. +def update_cached_appointments_sync(): + global cached_appointments, last_scraped_time + print(f"[{datetime.datetime.now()}] Performing synchronous scrape...") + try: + scraped_data = scrape_bokadirekt_appointments(SALON_URLS) + if scraped_data: + cached_appointments = scraped_data + last_scraped_time = datetime.datetime.now() + print(f"[{datetime.datetime.now()}] Synchronous scrape successful. {len(cached_appointments)} appointments cached.") + else: + print(f"[{datetime.datetime.now()}] Synchronous scraper returned no data.") + except Exception as e: + print(f"[{datetime.datetime.now()}] Error during synchronous scrape: {e}") + + +# --- API Endpoints --- +@app.route('/api/appointments', methods=['GET']) +def get_appointments(): + """ + Returns the latest available appointments from cache. + The cache is updated periodically by a background thread. + """ + # In a production environment, you might want to handle cache staleness + # differently, e.g., serve stale data while a fresh scrape runs. + # For this example, we rely on the background thread to keep it updated. + return jsonify({ + "data": cached_appointments, + "last_updated": last_scraped_time.isoformat() if last_scraped_time else "Never", "message": "Appointments data from Klipptider backend." + }) + +@app.route('/', methods=['GET']) +def health_check(): + return "Klipptider Backend is running!" + +# --- Main execution --- +if __name__ == '__main__': + # Perform an initial scrape to populate the cache immediately on startup. + print("Performing initial scrape...") + update_cached_appointments_sync() + print(f"Initial scrape complete. Cache size: {len(cached_appointments)}") + + # Start the periodic scraper in a background thread if there are URLs to scrape. + # Note: For production, consider using a dedicated task queue (e.g., Celery) or + # a cron job for robust background task management, instead of Flask's built-in threading. + if SALON_URLS: + print(f"Starting background scraper to update every {SCRAPE_INTERVAL_MINUTES} minutes.") + # Start the timer, it will call update_cached_appointments and then reschedule itself. + threading.Timer(SCRAPE_INTERVAL_MINUTES * 60, update_cached_appointments).start() + else: + print("No salon URLs configured, background scraper not started.") + + # Run the Flask app. + # Set debug=False for production deployments, as debug=True can interfere with threading.Timer. + app.run(host='0.0.0.0', port=5000, debug=False)