22import os
33from datetime import datetime
44from decimal import Decimal
5-
65from flask import Flask , jsonify , send_from_directory
76from flask .json import JSONEncoder
87from flask_cors import CORS
98from flask_limiter import Limiter
109from flask_limiter .util import get_remote_address
1110from flask_migrate import Migrate
12-
1311from src .config .settings import config
1412from src .models import db
1513from src .routes import api_bp
1614from src .utils .error_handlers import register_error_handlers
1715
1816
19- def create_app ():
17+ def create_app () -> Any :
2018 """Create and configure the Flask application."""
2119 app = Flask (__name__ , static_folder = "../web-frontend/dist" , static_url_path = "" )
2220
23- # ---------------------------
24- # Custom JSON encoder
25- # ---------------------------
2621 class CustomJSONEncoder (JSONEncoder ):
22+
2723 def default (self , obj ):
2824 if isinstance (obj , Decimal ):
29- # Convert Decimal to string so precision is preserved in JSON
3025 return str (obj )
3126 if isinstance (obj , datetime ):
32- # Convert datetime to ISO 8601 string
3327 return obj .isoformat ()
3428 return super ().default (obj )
3529
36- # Attach our JSON encoder (note: may raise a deprecation warning on very new Flask versions,
37- # but this works for most Flask releases).
3830 app .json_encoder = CustomJSONEncoder
39-
40- # ---------------------------
41- # Configuration
42- # ---------------------------
4331 config_name = os .environ .get ("FLASK_CONFIG" , "default" )
4432 app .config .from_object (config [config_name ])
45-
46- # validate critical configuration if the config object exposes this method
4733 if hasattr (config [config_name ], "validate_config" ):
4834 config [config_name ].validate_config ()
49-
50- # ---------------------------
51- # Ensure DB directory exists (for SQLite file-based DB)
52- # ---------------------------
5335 db_uri = app .config .get ("SQLALCHEMY_DATABASE_URI" , "" )
5436 if db_uri and db_uri .startswith ("sqlite:///" ):
55- # support only the common sqlite:///file.db form
56- # split only once so we don't accidentally replace multiple occurrences
5737 db_file = db_uri .split ("sqlite:///" , 1 )[1 ]
5838 db_dir = os .path .dirname (os .path .abspath (db_file )) or None
59- if db_dir and not os .path .exists (db_dir ):
39+ if db_dir and ( not os .path .exists (db_dir ) ):
6040 os .makedirs (db_dir , exist_ok = True )
61-
62- # ---------------------------
63- # Initialize extensions
64- # ---------------------------
6541 db .init_app (app )
6642 Migrate (app , db )
6743 Limiter (key_func = get_remote_address , app = app )
68-
69- # ---------------------------
70- # Configure CORS
71- # ---------------------------
7244 cors_origins = os .environ .get (
7345 "CORS_ORIGINS" , "http://localhost:3000,http://localhost:5000"
7446 ).split ("," )
75- # restrict resources explicitly
76- CORS (app , resources = {r"/*" : {"origins" : cors_origins }}, supports_credentials = True )
77-
78- # ---------------------------
79- # Configure logging
80- # ---------------------------
47+ CORS (app , resources = {"/*" : {"origins" : cors_origins }}, supports_credentials = True )
8148 logging .basicConfig (level = logging .INFO )
8249 logger = app .logger
8350 logger .info ("Starting Flask application" )
84-
85- # ---------------------------
86- # Register blueprints
87- # ---------------------------
8851 app .register_blueprint (api_bp )
8952
90- # ---------------------------
91- # Web frontend routes (serve SPA)
92- # ---------------------------
9353 @app .route ("/" )
9454 def serve_web_frontend ():
9555 """Serve the React web-frontend index page."""
@@ -101,27 +61,19 @@ def serve_static_files(path):
10161 try :
10262 return send_from_directory (app .static_folder , path )
10363 except Exception :
104- # Fallback to index.html for SPA routing (so client-side router works)
10564 return send_from_directory (app .static_folder , "index.html" )
10665
107- # ---------------------------
108- # Error handlers
109- # ---------------------------
11066 register_error_handlers (app )
11167
11268 @app .errorhandler (Exception )
11369 def handle_exception (e ):
114- # Log the error with traceback (do not return traceback to client)
11570 app .logger .error ("Unhandled Exception" , exc_info = True )
11671 response = {
11772 "status" : "error" ,
11873 "message" : "An internal server error occurred. Please try again later." ,
11974 }
120- return jsonify (response ), 500
75+ return ( jsonify (response ), 500 )
12176
122- # ---------------------------
123- # Database initialization (SQLite file creation)
124- # ---------------------------
12577 def _initialize_sqlite_if_missing ():
12678 """Create SQLite DB file and tables if using file-based SQLite and file is missing."""
12779 db_uri_local = app .config .get ("SQLALCHEMY_DATABASE_URI" , "" )
@@ -133,5 +85,4 @@ def _initialize_sqlite_if_missing():
13385 logger .info ("Database initialized successfully (created tables)" )
13486
13587 _initialize_sqlite_if_missing ()
136-
13788 return app
0 commit comments