import os from flask import Flask, request, jsonify from flask_cors import CORS from ingress import IngressAPI from models import EventType from pymongo import MongoClient from pymongo.errors import PyMongoError from dotenv import load_dotenv from lib import ( check_basic_auth, basic_auth_required, parse_timestamp, plext_to_dict, logger, ) load_dotenv() app = Flask(__name__) CORS(app) @app.route("/plexts/from-db", methods=["GET"]) @basic_auth_required def get_plexts_from_db(): """ Get plexts from MongoDB with optional filters. Returns plexts sorted by timestamp (most recent first). Query Parameters: player_name: Filter by player name (optional) timestamp_from: Minimum timestamp in milliseconds (optional) timestamp_to: Maximum timestamp in milliseconds (optional) limit: Maximum number of results to return (optional, default: 100) Returns: JSON response with list of plexts (without _id field), limited by the limit parameter """ try: # Parse query parameters player_name = request.args.get("player_name") timestamp_from = request.args.get("timestamp_from") timestamp_to = request.args.get("timestamp_to") limit_param = request.args.get("limit") # Validate and convert timestamp parameters to integers if provided if timestamp_from is not None: try: timestamp_from = int(timestamp_from) except ValueError: return jsonify({"error": "timestamp_from must be an integer"}), 400 if timestamp_to is not None: try: timestamp_to = int(timestamp_to) except ValueError: return jsonify({"error": "timestamp_to must be an integer"}), 400 # Validate and convert limit parameter to integer if provided, otherwise default to 100 if limit_param is not None: try: limit = int(limit_param) if limit <= 0: return jsonify({"error": "limit must be a positive integer"}), 400 except ValueError: return jsonify({"error": "limit must be an integer"}), 400 else: limit = 100 # Build MongoDB filter query filter_query = {} if player_name: filter_query["player_name"] = player_name if timestamp_from is not None: filter_query["timestamp"] = filter_query.get("timestamp", {}) filter_query["timestamp"]["$gte"] = timestamp_from if timestamp_to is not None: filter_query["timestamp"] = filter_query.get("timestamp", {}) filter_query["timestamp"]["$lte"] = timestamp_to # Connect to MongoDB mongo_uri = os.getenv("MONGO_URI") db_name = os.getenv("DB_NAME") collection_name = os.getenv("COLLECTION_NAME") client = MongoClient(mongo_uri) db = client[db_name] collection = db[collection_name] try: # Projection to exclude _id field projection = {"_id": 0} # Execute query with sorting (timestamp DESC - most recent first) and dynamic limit cursor = ( collection.find(filter=filter_query, projection=projection) .sort("timestamp", -1) .limit(limit) ) # Convert cursor to list plexts = list(cursor) return jsonify({"count": len(plexts), "plexts": plexts}), 200 except PyMongoError as e: logger.error(f"MongoDB error: {e}") return jsonify({"error": "Database error"}), 500 finally: client.close() except Exception as e: logger.exception("Unexpected error in get_plexts_from_db") return jsonify({"error": "An error occurred"}), 500 @app.route("/plexts/from-api", methods=["GET"]) @basic_auth_required def get_plexts_from_api(): """ Get plexts from the Ingress API. Query Parameters: event_types: List of event types to filter by (comma-separated) player_name: Player name to filter by min_lat: Minimum latitude (default: 45470259) min_lng: Minimum longitude (default: 12244155) max_lat: Maximum latitude (default: 45480370) max_lng: Maximum longitude (default: 12298207) min_timestamp: Minimum timestamp (milliseconds or ISO 8601 format, default: -1) max_timestamp: Maximum timestamp (milliseconds or ISO 8601 format, default: -1) Returns: JSON response with list of plexts """ try: # Parse query parameters event_types_param = request.args.get("event_types") event_types = None if event_types_param: event_types_list = [et.strip() for et in event_types_param.split(",")] try: event_types = [EventType[et] for et in event_types_list] except KeyError as e: return jsonify({"error": f"Invalid event type: {e}"}), 400 player_name = request.args.get("player_name") min_lat = int(request.args.get("min_lat", os.getenv("MIN_LAT"))) min_lng = int(request.args.get("min_lng", os.getenv("MIN_LNG"))) max_lat = int(request.args.get("max_lat", os.getenv("MAX_LAT"))) max_lng = int(request.args.get("max_lng", os.getenv("MAX_LNG"))) min_timestamp_param = request.args.get("min_timestamp", "-1") max_timestamp_param = request.args.get("max_timestamp", "-1") min_timestamp = parse_timestamp(min_timestamp_param) max_timestamp = parse_timestamp(max_timestamp_param) # Initialize IngressAPI client cookie = os.getenv("INGRESS_COOKIE") client = IngressAPI( version=os.getenv("V"), cookie=cookie, ) # Fetch plexts plexts = client.get_plexts( min_lat_e6=min_lat, min_lng_e6=min_lng, max_lat_e6=max_lat, max_lng_e6=max_lng, min_timestamp_ms=min_timestamp, max_timestamp_ms=max_timestamp, event_types=event_types, player_name=player_name, ) # Sort plexts by timestamp (ascending - oldest first) sorted_plexts = sorted(plexts, key=lambda p: p.timestamp) # Convert to JSON-serializable format result = [plext_to_dict(p) for p in sorted_plexts] return jsonify( { "count": len(result), "plexts": result, } ) except ValueError as e: logger.warning(f"Validation error: {e}") return jsonify({"error": str(e)}), 400 except Exception as e: logger.exception("Error processing request") return jsonify({"error": f"An error occurred: {e}"}), 500 @app.route("/", methods=["GET"]) def index(): """Root endpoint providing API information. Returns: JSON response containing API name, version, and available endpoints with their parameters and descriptions. """ return jsonify( { "name": "Ingress Intel API", "version": "1.0.0", "endpoints": { "/plexts/from-api": { "method": "GET", "description": "Get plexts from the Ingress API", "parameters": { "event_types": "List of event types to filter by (comma-separated)", "player_name": "Player name to filter by", "min_lat": "Minimum latitude (default: 45470259)", "min_lng": "Minimum longitude (default: 12244155)", "max_lat": "Maximum latitude (default: 45480370)", "max_lng": "Maximum longitude (default: 12298207)", "min_timestamp": "Minimum timestamp (milliseconds or ISO 8601 format, default: -1)", "max_timestamp": "Maximum timestamp (milliseconds or ISO 8601 format, default: -1)", }, "event_types": [e.name for e in EventType], }, "/plexts/from-db": { "method": "GET", "description": "Get plexts from MongoDB with optional filters. Returns plexts sorted by timestamp (most recent first).", "parameters": { "player_name": "Filter by player name (optional)", "timestamp_from": "Minimum timestamp in milliseconds (optional)", "timestamp_to": "Maximum timestamp in milliseconds (optional)", "limit": "Maximum number of results to return (optional, default: 100)", }, "authentication": "Basic Auth required", }, }, } ) if __name__ == "__main__": app.run(debug=True, host="0.0.0.0", port=os.getenv("PORT", 5000))