diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..3487166 Binary files /dev/null and b/.DS_Store differ diff --git a/articleServer/Dockerfile b/articleServer/Dockerfile new file mode 100644 index 0000000..318eb15 --- /dev/null +++ b/articleServer/Dockerfile @@ -0,0 +1,12 @@ +FROM python:3.9-slim + +WORKDIR /app + +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +COPY . . + +EXPOSE 5000 + +CMD ["python", "run_server.py"] diff --git a/articleServer/app.py b/articleServer/app.py new file mode 100644 index 0000000..d716ad9 --- /dev/null +++ b/articleServer/app.py @@ -0,0 +1,188 @@ +import os +import json +import datetime +from pathlib import Path +from flask import Flask, request, jsonify +from datetime import timedelta + +app = Flask(__name__) + +try: + # Read the RSS feeds to get all available news outlets + rss_feeds_path = os.path.join( + os.path.dirname(__file__), "..", "scraper", "rss_feeds.json" + ) + with open(rss_feeds_path, "r") as f: + rss_feeds = json.load(f) + + # Get all available news outlets from the RSS feeds + NEWS_OUTLETS = list(rss_feeds["rss_feeds"].keys()) +except Exception as e: + print(f"Error loading RSS feeds: {e}") + NEWS_OUTLETS = [] + +# Configuration - can be overridden by environment variable +ARTICLE_DIR = os.environ.get("ARTICLE_DIR", "/Volumes/WORKDIR/articles/") + +# Validate article directory exists +if not os.path.exists(ARTICLE_DIR): + print(f"Warning: Article directory does not exist: {ARTICLE_DIR}") + + +def get_files_in_directory(directory_path): + """Get all files in a directory recursively.""" + files = [] + for root, _, filenames in os.walk(directory_path): + for filename in filenames: + file_path = os.path.join(root, filename) + files.append(file_path) + return files + + +def get_file_create_time(file_path): + """Get the creation time of a file.""" + stat = os.stat(file_path) + # On Unix systems, we use the creation time (ctime) or modification time (mtime) + # On some systems like macOS, ctime might be more appropriate + return datetime.datetime.fromtimestamp(stat.st_ctime) + + +def is_file_in_time_range(file_path, start_time): + """Check if a file was created after the start_time.""" + try: + create_time = get_file_create_time(file_path) + return create_time >= start_time + except Exception: + # If we can't get the creation time, assume it's not in range + return False + + +def filter_articles_by_outlets(articles, outlets): + """Filter articles based on specified outlets.""" + if not outlets or not isinstance(outlets, list): + return articles + + # Normalize outlet names for comparison (remove extra spaces, make lowercase) + normalized_outlets = [outlet.strip().lower() for outlet in outlets] + + filtered_articles = [] + for article in articles: + # Extract the news outlet from the file path + # Article paths are like: /path/to/articles/Reuters – Business News/article_name.txt + path_parts = Path(article).parts + if len(path_parts) >= 2: + outlet_name = path_parts[-2] # Outlet name is second to last part + if outlet_name.lower() in normalized_outlets: + filtered_articles.append(article) + + return filtered_articles + + +def get_articles_in_time_range(time_range, outlets=None): + """ + Get articles within a specified time range from the article directory. + + Args: + time_range (str): Time range ('hour', 'day', 'week', 'month') + outlets (list, optional): List of news outlets to filter by + + Returns: + list: List of article file paths matching criteria + """ + # Validate that the article directory exists + if not os.path.exists(ARTICLE_DIR): + return [] + + # Get the start time based on the time range + now = datetime.datetime.now() + + if time_range == "hour": + start_time = now - timedelta(hours=1) + elif time_range == "day": + start_time = now - timedelta(days=1) + elif time_range == "week": + start_time = now - timedelta(weeks=1) + elif time_range == "month": + start_time = now - timedelta(days=30) + else: + # Default to last hour if not specified correctly + start_time = now - timedelta(hours=1) + + # Get all files under the article directory + all_articles = get_files_in_directory(ARTICLE_DIR) + + # Filter for articles within time range + filtered_articles = [] + for article_path in all_articles: + if is_file_in_time_range(article_path, start_time): + filtered_articles.append(article_path) + + # Filter by outlets if provided + if outlets: + filtered_articles = filter_articles_by_outlets(filtered_articles, outlets) + + return filtered_articles + + +@app.route("/articles", methods=["GET"]) +def articles_endpoint(): + """HTTP endpoint to get articles.""" + try: + # Get query parameters + time_range = request.args.get("time_range", "hour").lower() + outlet_param = request.args.get("outlets") + + # Parse outlets if provided + outlets = None + if outlet_param: + outlets = [o.strip() for o in outlet_param.split(",") if o.strip()] + + # Validate time range + valid_time_ranges = ["hour", "day", "week", "month"] + if time_range not in valid_time_ranges: + return jsonify( + {"error": f"Invalid time_range. Must be one of {valid_time_ranges}"} + ), 400 + + # Get articles + articles = get_articles_in_time_range(time_range, outlets) + + response_data = { + "articles": [ + { + "path": article, + "name": os.path.basename(article), + "outlet": os.path.basename(os.path.dirname(article)), + "created_at": get_file_create_time(article).isoformat(), + } + for article in articles + ], + "count": len(articles), + "time_range": time_range, + "outlets": outlets if outlets else "all", + } + + return jsonify(response_data) + + except Exception as e: + return jsonify({"error": str(e)}), 500 + + +@app.route("/outlets", methods=["GET"]) +def outlets_endpoint(): + """HTTP endpoint to get all available news outlets.""" + try: + response_data = {"news_outlets": NEWS_OUTLETS, "count": len(NEWS_OUTLETS)} + return jsonify(response_data) + except Exception as e: + return jsonify({"error": str(e)}), 500 + + +@app.route("/health", methods=["GET"]) +def health_check(): + """Health check endpoint.""" + return jsonify({"status": "healthy"}) + + +if __name__ == "__main__": + app.run(host="0.0.0.0", port=5008, debug=True) diff --git a/articleServer/pyvenv.cfg b/articleServer/pyvenv.cfg new file mode 100644 index 0000000..52a9ca7 --- /dev/null +++ b/articleServer/pyvenv.cfg @@ -0,0 +1,5 @@ +home = /opt/homebrew/opt/python@3.13/bin +include-system-site-packages = false +version = 3.13.7 +executable = /opt/homebrew/Cellar/python@3.13/3.13.7/Frameworks/Python.framework/Versions/3.13/bin/python3.13 +command = /opt/homebrew/opt/python@3.13/bin/python3.13 -m venv /Users/user/Projects/StockDocs/articleServer diff --git a/articleServer/requirements.txt b/articleServer/requirements.txt new file mode 100644 index 0000000..e3e9a71 --- /dev/null +++ b/articleServer/requirements.txt @@ -0,0 +1 @@ +Flask diff --git a/articleServer/run_server.py b/articleServer/run_server.py new file mode 100755 index 0000000..7f99025 --- /dev/null +++ b/articleServer/run_server.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 + +import os +import sys + +# Add the current directory to Python path +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + +from app import app + +if __name__ == "__main__": + # Ensure ARTICLE_DIR environment variable is set if not already + if "ARTICLE_DIR" not in os.environ: + print("Warning: ARTICLE_DIR environment variable not set. Using default path.") + + app.run(host="0.0.0.0", port=5000, debug=True)