diff --git a/lib/src/http/controller/controller_handler.dart b/lib/src/http/controller/controller_handler.dart index 41387f7..4ceb1b5 100644 --- a/lib/src/http/controller/controller_handler.dart +++ b/lib/src/http/controller/controller_handler.dart @@ -1,5 +1,6 @@ import 'package:vania/src/exception/validation_exception.dart'; import 'package:vania/src/route/route_data.dart'; +import 'package:vania/src/route/route_history.dart'; import 'package:vania/vania.dart'; class ControllerHandler { @@ -28,9 +29,14 @@ class ControllerHandler { response.makeResponse(request.response); } on ValidationException catch (error) { - error - .response(request.headers['accept'].toString().contains('html')) - .makeResponse(request.response); + bool isHtml = + request.request.headers.value('accept').toString().contains('html'); + if (isHtml) { + Response.redirect(RouteHistory().previousRoute) + .makeResponse(request.response); + } else { + error.response(false).makeResponse(request.response); + } } catch (error) { _response(request, error.toString()); } diff --git a/lib/src/http/request/request.dart b/lib/src/http/request/request.dart index c3f958a..d3bf7d5 100644 --- a/lib/src/http/request/request.dart +++ b/lib/src/http/request/request.dart @@ -4,6 +4,7 @@ import 'package:vania/src/exception/validation_exception.dart'; import 'package:vania/src/http/request/request_body.dart'; import 'package:vania/src/http/validation/validator.dart'; import 'package:vania/src/route/route_data.dart'; +import 'package:vania/src/view_engine/template_engine.dart'; import 'package:vania/vania.dart'; class Request { @@ -321,6 +322,7 @@ class Request { [Map messages = const {}]) { assert(rules is Map || rules is List, 'Rules must be either Map or List.'); + TemplateEngine().sessionErrors.clear(); if (rules is Map) { _validate(rules, messages); } else { @@ -336,6 +338,10 @@ class Request { } validator.validate(rules); if (validator.hasError) { + bool isHtml = request.headers.value('accept').toString().contains('html'); + if (isHtml) { + TemplateEngine().sessionErrors.addAll(validator.errors); + } throw ValidationException(message: validator.errors); } } @@ -354,6 +360,10 @@ class Request { } } if (errors.isNotEmpty) { + bool isHtml = request.headers.value('accept').toString().contains('html'); + if (isHtml) { + TemplateEngine().sessionErrors.addAll(errors); + } throw ValidationException(message: errors); } } diff --git a/lib/src/http/request/request_handler.dart b/lib/src/http/request/request_handler.dart index b95038f..3c7accd 100644 --- a/lib/src/http/request/request_handler.dart +++ b/lib/src/http/request/request_handler.dart @@ -9,6 +9,8 @@ import 'package:vania/src/http/controller/controller_handler.dart'; import 'package:vania/src/http/middleware/middleware_handler.dart'; import 'package:vania/src/route/route_data.dart'; import 'package:vania/src/route/route_handler.dart'; +import 'package:vania/src/route/route_history.dart'; +import 'package:vania/src/view_engine/template_engine.dart'; import 'package:vania/src/websocket/web_socket_handler.dart'; import 'package:vania/vania.dart'; @@ -36,6 +38,8 @@ Future httpRequestHandler(HttpRequest req) async { String requestUri = req.uri.path; String starteRequest = startTime.format(); + bool isHtml = req.headers.value('accept').toString().contains('html'); + try { /// Check if cors is enabled HttpCors(req); @@ -44,6 +48,12 @@ Future httpRequestHandler(HttpRequest req) async { await request.extractBody(); if (route == null) return; + RouteHistory().updateRouteHistory(req); + + if (isHtml) { + TemplateEngine().formData.addAll(request.all()); + } + /// check if pre middleware exist and call it if (route.preMiddleware.isNotEmpty) { await middlewareHandler(route.preMiddleware, request); @@ -55,7 +65,6 @@ Future httpRequestHandler(HttpRequest req) async { request: request, ); } on BaseHttpResponseException catch (error) { - bool isHtml = req.headers.value('accept').toString().contains('html'); if (error is NotFoundException && isHtml) { if (File('lib/view/template/errors/404.html').existsSync()) { return view('errors/404').makeResponse(req.response); diff --git a/lib/src/http/response/response.dart b/lib/src/http/response/response.dart index 7d3ee7c..61bc7ac 100644 --- a/lib/src/http/response/response.dart +++ b/lib/src/http/response/response.dart @@ -3,6 +3,8 @@ import 'dart:io'; import 'dart:typed_data'; import 'package:meta/meta.dart'; import 'package:vania/src/http/response/stream_file.dart'; +import 'package:vania/src/route/route_history.dart'; +import 'package:vania/src/view_engine/template_engine.dart'; enum ResponseType { json, @@ -191,4 +193,23 @@ class Response { responseType: ResponseType.download, headers: headers, ); + + static back([String? key, String? message]) { + String previousRoute = RouteHistory().previousRoute; + if (key != null && message != null) { + TemplateEngine().sessions[key] = message; + } + if (previousRoute.isNotEmpty) { + return Response( + responseType: ResponseType.redirect, + data: previousRoute, + httpStatusCode: HttpStatus.found, + ); + } + return Response( + responseType: ResponseType.redirect, + data: RouteHistory().currentRoute, + httpStatusCode: HttpStatus.found, + ); + } } diff --git a/lib/src/route/route_handler.dart b/lib/src/route/route_handler.dart index 5b480ba..50e7c59 100644 --- a/lib/src/route/route_handler.dart +++ b/lib/src/route/route_handler.dart @@ -6,6 +6,20 @@ import 'package:vania/src/route/set_static_path.dart'; import 'package:vania/src/utils/functions.dart'; import 'package:vania/vania.dart'; +/// Find the matched route from the given request and return the +/// [RouteData] for the matched route. +/// +/// The function first checks if the request is an OPTIONS request. If it is, +/// the request is closed and null is returned. If the request is not an +/// OPTIONS request, the function checks if the request path matches a static +/// file. If it does, the function returns null. If it doesn't, the function +/// throws a [NotFoundException]. +/// +/// If the request is not an OPTIONS request and the request path doesn't match +/// a static file, the function returns the matched [RouteData]. +/// +/// Throws a [NotFoundException] if the request is not an OPTIONS request and +/// the request path doesn't match a static file. RouteData? httpRouteHandler(HttpRequest req) { final route = _getMatchRoute( Uri.decodeComponent( @@ -36,7 +50,6 @@ RouteData? httpRouteHandler(HttpRequest req) { return route; } -/// Exctract the domain from the url String _extractDomain(String domain, String path) { String firstPart = domain.split('.').first.toLowerCase(); final RegExp domainRegex = RegExp(r'\{[^}]*\}'); @@ -48,8 +61,6 @@ String _extractDomain(String domain, String path) { return domainUri; } -/// Exctarct username from {username} -/// Or any string between {} String? _extractDomainPlaceholder(String input) { final RegExp regex = RegExp(r'\{([^}]*)\}'); final match = regex.firstMatch(input); diff --git a/lib/src/route/route_history.dart b/lib/src/route/route_history.dart new file mode 100644 index 0000000..23c82b4 --- /dev/null +++ b/lib/src/route/route_history.dart @@ -0,0 +1,24 @@ +import 'dart:io'; + +class RouteHistory { + static final RouteHistory _instance = RouteHistory._internal(); + factory RouteHistory() => _instance; + RouteHistory._internal(); + + String _currentRoute = ''; + String _previousRoute = ''; + + String get currentRoute => _currentRoute; + String get previousRoute => _previousRoute; + + Future updateRouteHistory(HttpRequest req) async { + if (req.headers.value('accept').toString().contains('html')) { + if (_currentRoute.isEmpty) { + _currentRoute = req.uri.path; + } else { + _previousRoute = _currentRoute; + _currentRoute = req.uri.path; + } + } + } +} diff --git a/lib/src/utils/helper.dart b/lib/src/utils/helper.dart index dcf90c8..1229846 100644 --- a/lib/src/utils/helper.dart +++ b/lib/src/utils/helper.dart @@ -59,7 +59,7 @@ Response view(String template, [Map? context]) => Future setSession(String key, dynamic value) async => await SessionManager().setSession(key, value); Future getSession(String key) async => - await SessionManager().getSession(key); + TemplateEngine().sessions[key] ?? await SessionManager().getSession(key); Future?> allSessions() async => await SessionManager().allSessions(); Future deleteSession(String key) async => diff --git a/lib/src/view_engine/processor_engine/error_processor.dart b/lib/src/view_engine/processor_engine/error_processor.dart new file mode 100644 index 0000000..588abb9 --- /dev/null +++ b/lib/src/view_engine/processor_engine/error_processor.dart @@ -0,0 +1,29 @@ +import 'package:vania/src/view_engine/template_engine.dart'; + +import 'abs_processor.dart'; + +class ErrorProcessor extends AbsProcessor { + @override + String parse(String content, [Map? context]) { + final hasErrorPattern = RegExp( + r"hasError\(\s*'([^']*)'\s*\)", + dotAll: true, + ); + content = content.replaceAllMapped(hasErrorPattern, (match) { + final errorKey = match.group(1); + return TemplateEngine().sessionErrors.containsKey(errorKey).toString(); + }); + + final errorPattern = RegExp( + r"\{@\s*error\(\s*'([^']*)'\s*\)\s*@\}", + dotAll: true, + ); + + content = content.replaceAllMapped(errorPattern, (error) { + final errorKey = error.group(1); + return TemplateEngine().sessionErrors[errorKey] ?? ''; + }); + + return content; + } +} diff --git a/lib/src/view_engine/processor_engine/old_processor.dart b/lib/src/view_engine/processor_engine/old_processor.dart new file mode 100644 index 0000000..4f56668 --- /dev/null +++ b/lib/src/view_engine/processor_engine/old_processor.dart @@ -0,0 +1,20 @@ +import 'package:vania/src/view_engine/template_engine.dart'; + +import 'abs_processor.dart'; + +class OldProcessor extends AbsProcessor { + @override + String parse(String content, [Map? context]) { + final oldPattern = RegExp( + r"\{@\s*old\(\s*'([^']*)'\s*\)\s*@\}", + dotAll: true, + ); + + content = content.replaceAllMapped(oldPattern, (oldMatch) { + final oldKey = oldMatch.group(1); + return TemplateEngine().formData[oldKey] ?? ''; + }); + + return content; + } +} diff --git a/lib/src/view_engine/processor_engine/session_processor.dart b/lib/src/view_engine/processor_engine/session_processor.dart new file mode 100644 index 0000000..899a594 --- /dev/null +++ b/lib/src/view_engine/processor_engine/session_processor.dart @@ -0,0 +1,29 @@ +import 'package:vania/src/view_engine/processor_engine/abs_processor.dart'; +import 'package:vania/src/view_engine/template_engine.dart'; + +class SessionProcessor implements AbsProcessor { + @override + String parse(String content, [Map? context]) { + final hasSessionPattern = RegExp( + r"hasSession\(\s*'([^']*)'\s*\)", + dotAll: true, + ); + + content = content.replaceAllMapped(hasSessionPattern, (match) { + final sessionKey = match.group(1); + return TemplateEngine().sessions.containsKey(sessionKey).toString(); + }); + + final sessionPattern = RegExp( + r"\{@\s*session\(\s*'([^']*)'\s*\)\s*@\}", + dotAll: true, + ); + + content = content.replaceAllMapped(sessionPattern, (math) { + final sessionKey = math.group(1); + return TemplateEngine().sessions[sessionKey] ?? ''; + }); + + return content; + } +} diff --git a/lib/src/view_engine/template_engine.dart b/lib/src/view_engine/template_engine.dart index 866d498..0ec178e 100644 --- a/lib/src/view_engine/template_engine.dart +++ b/lib/src/view_engine/template_engine.dart @@ -3,11 +3,14 @@ import 'package:vania/src/view_engine/processor_engine/variables_processor.dart' import 'processor_engine/csrf_processor.dart'; import 'processor_engine/csrf_token_processor.dart'; +import 'processor_engine/error_processor.dart'; import 'processor_engine/if_statement_processor.dart'; import 'processor_engine/extends_processor.dart'; import 'processor_engine/for_loop_processor.dart'; import 'processor_engine/include_processor.dart'; +import 'processor_engine/old_processor.dart'; import 'processor_engine/section_processor.dart'; +import 'processor_engine/session_processor.dart'; import 'processor_engine/switch_cases_processor.dart'; import 'template_reader.dart'; @@ -28,6 +31,7 @@ class TemplateEngine { static final TemplateEngine _singleton = TemplateEngine._internal(); factory TemplateEngine() => _singleton; TemplateEngine._internal(); + final VariablesProcessor _variablesProcess = VariablesProcessor(); final IfStatementProcessor _conditionalProcess = IfStatementProcessor(); final SwitchCasesProcessor _switchCaseProcess = SwitchCasesProcessor(); @@ -36,11 +40,22 @@ class TemplateEngine { final ExtendsProcessor _extendsProcessor = ExtendsProcessor(); final CsrfProcessor _csrfProcessor = CsrfProcessor(); final CsrfTokenProcessor _csrfTokenProcessor = CsrfTokenProcessor(); + final ErrorProcessor _errorProcessor = ErrorProcessor(); final SectionProcessor _sectionProcessor = SectionProcessor(); + final OldProcessor _oldProcessor = OldProcessor(); + final SessionProcessor _sessionProcessor = SessionProcessor(); + + final Map sessionErrors = {}; + final Map formData = {}; + final Map sessions = {}; String render(String template, [Map? data]) { String templateContent = FileTemplateReader().read(template); - return renderString(templateContent, data); + String renderedTemplate = renderString(templateContent, data); + sessionErrors.clear(); + formData.clear(); + sessions.clear(); + return renderedTemplate; } /// Renders a template string with the provided data context. @@ -69,12 +84,15 @@ class TemplateEngine { _extendsProcessor, _includeProcessor, _sectionProcessor, + _errorProcessor, + _sessionProcessor, _forLoopProcessor, _switchCaseProcess, _conditionalProcess, _variablesProcess, _csrfProcessor, _csrfTokenProcessor, + _oldProcessor ]); final renderedContent = pipeline.run(templateContent, data);