|
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, 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 |
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,7 +97,7 @@ 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(): |
@@ -136,6 +160,17 @@ class DefaultArgs: |
136 | 160 | scheduler.init_app(app) |
137 | 161 | scheduler.start() |
138 | 162 |
|
| 163 | + @scheduler.task("interval", id="cleanse_token_blocklist", seconds=86400) # 24 hours |
| 164 | + def cleanse_token_blocklist(): |
| 165 | + """Remove expired tokens from blocklist so the collection doesn't grow forever.""" |
| 166 | + from datetime import timezone |
| 167 | + from src.database import db |
| 168 | + result = db["token_blocklist"].delete_many( |
| 169 | + {"expires_at": {"$lt": datetime.now(timezone.utc)}} |
| 170 | + ) |
| 171 | + if result.deleted_count: |
| 172 | + logging.info(f"Cleansed {result.deleted_count} expired token(s) from blocklist") |
| 173 | + |
139 | 174 | @scheduler.task("interval", id="scrape_schedules", seconds=43200) # 12 hours |
140 | 175 | def scrape_schedules(): |
141 | 176 | logging.info("Scraping game schedules...") |
|
0 commit comments