1- from jsweb .database import init_db
2- from jsweb .routing import Router
3- from jsweb .request import Request
4- from jsweb .static import serve_static
5- # Assuming you have a response module as suggested
6- from jsweb .response import Response , HTMLResponse
7-
1+ import secrets
2+ import os
3+ from .routing import Router
4+ from .request import Request
5+ from .response import Response , HTMLResponse , configure_template_env
6+ from .auth import init_auth , get_current_user
7+ from .middleware import StaticFilesMiddleware , DBSessionMiddleware , CSRFMiddleware
8+ from .blueprints import Blueprint
89
910class JsWebApp :
1011 """
1112 The main application class for the JsWeb framework.
12-
13- It is responsible for routing requests and is configurable.
14- Database schema management should be handled by a separate CLI command.
1513 """
16- def __init__ (self , static_url = "/static" , static_dir = "static" , template_dir = "templates" , db_url = None ):
14+ def __init__ (self , config ):
1715 self .router = Router ()
1816 self .template_filters = {}
19- if db_url :
20- init_db (db_url )
21- # Make static and template paths configurable
22- self .static_url = static_url
23- self .static_dir = static_dir
24- self .template_dir = template_dir
25-
26- def route (self , path , methods = None ):
27- """A decorator to register a view function for a given URL path."""
28- if methods is None :
29- methods = ["GET" ]
30- return self .router .route (path , methods )
17+ self .config = config
18+ self ._init_from_config () # Initial setup
3119
32- def filter (self , name ):
33- """
34- A decorator to register a custom filter for use in templates.
35- The filter is registered with this specific app instance.
36- """
20+ def _init_from_config (self ):
21+ """Initializes components that depend on the config."""
22+ if hasattr (self .config , "TEMPLATE_FOLDER" ) and hasattr (self .config , "BASE_DIR" ):
23+ template_path = os .path .join (self .config .BASE_DIR , self .config .TEMPLATE_FOLDER )
24+ configure_template_env (template_path )
25+
26+ if hasattr (self .config , "SECRET_KEY" ):
27+ init_auth (self .config .SECRET_KEY , self ._get_actual_user_loader ())
28+
29+ def _get_actual_user_loader (self ):
30+ if hasattr (self , '_user_loader_callback' ) and self ._user_loader_callback :
31+ return self ._user_loader_callback
32+ return self .user_loader
3733
34+ def user_loader (self , user_id : int ):
35+ try :
36+ from models import User
37+ return User .query .get (user_id )
38+ except (ImportError , AttributeError ):
39+ return None
40+
41+ def route (self , path , methods = None , endpoint = None ):
42+ return self .router .route (path , methods , endpoint )
43+
44+ def register_blueprint (self , blueprint : Blueprint ):
45+ """Registers a blueprint with the application."""
46+ for path , handler , methods , endpoint in blueprint .routes :
47+ full_path = path
48+ if blueprint .url_prefix :
49+ full_path = f"{ blueprint .url_prefix .rstrip ('/' )} /{ path .lstrip ('/' )} "
50+
51+ # Create a prefixed endpoint, e.g., "auth.login"
52+ full_endpoint = f"{ blueprint .name } .{ endpoint } "
53+ self .router .add_route (full_path , handler , methods , endpoint = full_endpoint )
54+
55+ def filter (self , name ):
3856 def decorator (func ):
3957 self .template_filters [name ] = func
4058 return func
41-
4259 return decorator
4360
44- def __call__ (self , environ , start_response ):
45- """The main WSGI entry point."""
46- req = Request (environ )
47-
48- # Handle static files using the configured path
49- if req .path .startswith (self .static_url ):
50- content , status , headers = serve_static (req .path , self .static_url , self .static_dir )
51- start_response (status , headers )
52- # Ensure content is bytes
53- return [content if isinstance (content , bytes ) else content .encode ("utf-8" )]
61+ def _wsgi_app_handler (self , environ , start_response ):
62+ req = environ ['jsweb.request' ]
5463
55- # Resolve and handle dynamic routes
56- handler = self .router .resolve (req .path , req .method )
64+ handler , params = self .router .resolve (req .path , req .method )
5765 if handler :
58- response = handler (req )
66+ response = handler (req , ** params )
5967
60- # If a handler returns a raw string, wrap it in a default response object
6168 if isinstance (response , str ):
6269 response = HTMLResponse (response )
6370
64- # If it's not a Response object, it's an error
6571 if not isinstance (response , Response ):
6672 raise TypeError (f"View function did not return a Response object (got { type (response ).__name__ } )" )
6773
68- # Convert our Response object to what the WSGI server needs
74+ if hasattr (req , 'new_csrf_token_generated' ) and req .new_csrf_token_generated :
75+ response .set_cookie ("csrf_token" , req .csrf_token , httponly = False , samesite = 'Lax' )
76+
6977 body_bytes , status , headers = response .to_wsgi ()
7078 start_response (status , headers )
7179 return [body_bytes ]
7280
73- # Handle 404 Not Found
7481 start_response ("404 Not Found" , [("Content-Type" , "text/html" )])
75- return [b"<h1>404 Not Found</h1>" ]
82+ return [b"<h1>404 Not Found</h1>" ]
83+
84+ def __call__ (self , environ , start_response ):
85+ # Create the Request object ONCE and pass the app instance to it.
86+ req = Request (environ , self )
87+ environ ['jsweb.request' ] = req
88+
89+ csrf_token = req .cookies .get ("csrf_token" )
90+ req .new_csrf_token_generated = False
91+ if not csrf_token :
92+ csrf_token = secrets .token_hex (32 )
93+ req .new_csrf_token_generated = True
94+ req .csrf_token = csrf_token
95+
96+ if hasattr (self .config , "SECRET_KEY" ):
97+ req .user = get_current_user (req )
98+
99+ static_url = getattr (self .config , "STATIC_URL" , "/static" )
100+ static_dir = getattr (self .config , "STATIC_DIR" , "static" )
101+
102+ handler = self ._wsgi_app_handler
103+ handler = DBSessionMiddleware (handler )
104+ handler = StaticFilesMiddleware (handler , static_url , static_dir )
105+ handler = CSRFMiddleware (handler )
106+
107+ return handler (environ , start_response )
0 commit comments