|
1 | 1 | import logging |
2 | 2 | import argparse |
3 | | -from flask import Flask, request, g |
| 3 | +import signal |
| 4 | +import sys |
4 | 5 | import time |
| 6 | +from datetime import datetime, timedelta |
| 7 | + |
| 8 | +from dotenv import load_dotenv |
| 9 | + |
| 10 | +load_dotenv() |
| 11 | + |
| 12 | +from flask import Flask, jsonify, request, g |
| 13 | +from flask_cors import CORS |
| 14 | +from flask_jwt_extended import JWTManager |
5 | 15 | from flask_graphql import GraphQLView |
6 | 16 | from graphene import Schema |
7 | 17 | from src.schema import Query, Mutation |
8 | 18 | from src.scrapers.games_scraper import fetch_game_schedule |
9 | 19 | from src.scrapers.youtube_stats import fetch_videos |
10 | 20 | from src.scrapers.daily_sun_scrape import fetch_news |
11 | 21 | from src.services.article_service import ArticleService |
| 22 | +from src.utils.constants import JWT_SECRET_KEY |
12 | 23 | from src.utils.team_loader import TeamLoader |
13 | | -import signal |
14 | | -import sys |
15 | | -from dotenv import load_dotenv |
16 | | - |
17 | | -load_dotenv() |
| 24 | +from src.database import db, client |
18 | 25 |
|
19 | 26 | app = Flask(__name__) |
20 | 27 |
|
| 28 | +# CORS: allow frontend (different origin) to call this API |
| 29 | +CORS(app, supports_credentials=True) |
| 30 | + |
| 31 | +# JWT config |
| 32 | +app.config["JWT_SECRET_KEY"] = JWT_SECRET_KEY |
| 33 | +app.config["JWT_ACCESS_TOKEN_EXPIRES"] = timedelta(hours=1) |
| 34 | +app.config["JWT_REFRESH_TOKEN_EXPIRES"] = timedelta(days=30) |
| 35 | + |
| 36 | +jwt = JWTManager(app) |
| 37 | + |
| 38 | + |
| 39 | +@jwt.token_in_blocklist_loader |
| 40 | +def check_if_token_revoked(jwt_header, jwt_payload: dict) -> bool: |
| 41 | + """Reject the request if the token's jti is in the blocklist (e.g. after logout).""" |
| 42 | + jti = jwt_payload["jti"] |
| 43 | + return db["token_blocklist"].find_one({"jti": jti}) is not None |
| 44 | + |
21 | 45 |
|
22 | 46 | @app.before_request |
23 | 47 | def start_timer(): |
@@ -73,13 +97,22 @@ def log_response_time(response): |
73 | 97 | datefmt="%Y-%m-%d %H:%M:%S", |
74 | 98 | ) |
75 | 99 |
|
76 | | -schema = Schema(query=Query, mutation=Mutation) |
| 100 | +schema = Schema(query=Query, mutation=Mutation, auto_camelcase=True) |
77 | 101 |
|
78 | 102 |
|
79 | 103 | def create_context(): |
80 | 104 | return {"team_loader": TeamLoader()} |
81 | 105 |
|
82 | 106 |
|
| 107 | +@app.route("/health") |
| 108 | +def health_check(): |
| 109 | + try: |
| 110 | + client.admin.command("ping") |
| 111 | + return jsonify({"status": "healthy", "database": "connected"}), 200 |
| 112 | + except Exception: |
| 113 | + return jsonify({"status": "unhealthy", "database": "disconnected"}), 503 |
| 114 | + |
| 115 | + |
83 | 116 | app.add_url_rule( |
84 | 117 | "/graphql", |
85 | 118 | view_func=GraphQLView.as_view( |
@@ -136,6 +169,17 @@ class DefaultArgs: |
136 | 169 | scheduler.init_app(app) |
137 | 170 | scheduler.start() |
138 | 171 |
|
| 172 | + @scheduler.task("interval", id="cleanse_token_blocklist", seconds=86400) # 24 hours |
| 173 | + def cleanse_token_blocklist(): |
| 174 | + """Remove expired tokens from blocklist so the collection doesn't grow forever.""" |
| 175 | + from datetime import timezone |
| 176 | + from src.database import db |
| 177 | + result = db["token_blocklist"].delete_many( |
| 178 | + {"expires_at": {"$lt": datetime.now(timezone.utc)}} |
| 179 | + ) |
| 180 | + if result.deleted_count: |
| 181 | + logging.info(f"Cleansed {result.deleted_count} expired token(s) from blocklist") |
| 182 | + |
139 | 183 | @scheduler.task("interval", id="scrape_schedules", seconds=43200) # 12 hours |
140 | 184 | def scrape_schedules(): |
141 | 185 | logging.info("Scraping game schedules...") |
|
0 commit comments