diff --git a/config/settings/base.py b/config/settings/base.py index 333066c..6c9b10c 100644 --- a/config/settings/base.py +++ b/config/settings/base.py @@ -119,6 +119,8 @@ MIDDLEWARE = [ "whitenoise.middleware.WhiteNoiseMiddleware", 'django.contrib.sessions.middleware.SessionMiddleware', 'corsheaders.middleware.CorsMiddleware', + # Subdomain routing - must be before LocaleMiddleware + 'config.subdomain_middleware.SubdomainRoutingMiddleware', 'django.middleware.locale.LocaleMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', diff --git a/config/subdomain_middleware.py b/config/subdomain_middleware.py new file mode 100644 index 0000000..2b52050 --- /dev/null +++ b/config/subdomain_middleware.py @@ -0,0 +1,106 @@ +""" +Subdomain Routing Middleware + +Routes requests to different URL configurations based on the subdomain. +This allows clean separation of admin, API, and docs URLs. + +Subdomains: +- admin.imamjavad.nwhco.ir → config.urls_admin +- api.imamjavad.nwhco.ir → config.urls_api +- docs.imamjavad.nwhco.ir → config.urls_docs +- imamjavad.nwhco.ir → config.urls (main/frontend) +""" +from django.utils.deprecation import MiddlewareMixin +from django.conf import settings + + +class SubdomainRoutingMiddleware(MiddlewareMixin): + """ + Middleware that sets the ROOT_URLCONF based on the request's subdomain. + """ + + # Mapping of subdomain prefixes to URL configurations + SUBDOMAIN_URLCONFS = { + 'admin': 'config.urls_admin', # Admin + Swagger/Redoc + } + + # List of main domains (without subdomain routing) + MAIN_DOMAINS = [ + 'imamjavad.nwhco.ir', + 'imamjavad.newhorizonco.uk', + 'dovoodi.nwhco.ir', + 'dovodi.newhorizonco.uk', + 'localhost', + '127.0.0.1', + ] + + def process_request(self, request): + """ + Determine the subdomain and set the appropriate ROOT_URLCONF. + """ + # Get host from request (handles X-Forwarded-Host for proxied requests) + host = request.get_host().split(':')[0].lower() + + # Check if this is a subdomain request + subdomain = self._extract_subdomain(host) + + if subdomain and subdomain in self.SUBDOMAIN_URLCONFS: + # Set the URL configuration for this subdomain + request.urlconf = self.SUBDOMAIN_URLCONFS[subdomain] + # Store subdomain info for potential use in views + request.subdomain = subdomain + else: + # Use default URL configuration + request.subdomain = None + + return None + + def _extract_subdomain(self, host): + """ + Extract the subdomain from the host. + + Examples: + - admin.imamjavad.nwhco.ir → 'admin' + - api.imamjavad.nwhco.ir → 'api' + - imamjavad.nwhco.ir → None + """ + # Check against main domains + for main_domain in self.MAIN_DOMAINS: + if host == main_domain: + return None + + # Check if host is a subdomain of a main domain + if host.endswith('.' + main_domain): + # Extract subdomain part + subdomain = host[:-len('.' + main_domain)] + # Only return first-level subdomain + if '.' not in subdomain: + return subdomain + + # Also check against ALLOWED_HOSTS for flexibility + for allowed_host in getattr(settings, 'ALLOWED_HOSTS', []): + if allowed_host.startswith('.'): + # Wildcard domain like .nwhco.ir + base_domain = allowed_host[1:] + if host.endswith(base_domain) and host != base_domain: + subdomain = host[:-len(base_domain)].rstrip('.') + if '.' not in subdomain: + return subdomain + + return None + + +def get_subdomain(request): + """ + Utility function to get the current subdomain from request. + Can be used in views or templates. + """ + return getattr(request, 'subdomain', None) + + +def is_admin_subdomain(request): + """Check if current request is on admin subdomain.""" + return get_subdomain(request) == 'admin' + + + diff --git a/config/urls_admin.py b/config/urls_admin.py new file mode 100644 index 0000000..4266f76 --- /dev/null +++ b/config/urls_admin.py @@ -0,0 +1,62 @@ +""" +URL configuration for Admin Subdomain (admin.imamjavad.nwhco.ir) + +This handles all admin-related URLs and Swagger/Redoc when accessed via the admin subdomain. +""" +from django.urls import path, include, re_path +from django.conf import settings +from django.conf.urls.static import static +from django.conf.urls.i18n import i18n_patterns + +from drf_yasg.views import get_schema_view +from drf_yasg import openapi + +from utils.admin import project_admin_site, dovoodi_admin_site, HomeView +from apps.api.views import CustomAPIDocumentationView, CustomSwaggerView, SwaggerTokenAuthView, clear_swagger_auth +from apps.api.permissions import IsAdminOrSwaggerToken +from apps.api.decorators import swagger_auth_required + +# Schema view for documentation +schema_view = get_schema_view( + openapi.Info( + title="Imam Javad API", + default_version='v1', + description="Comprehensive API documentation for the Imam Javad educational platform", + contact=openapi.Contact(email="contact@imamjavad.com"), + license=openapi.License(name="MIT License"), + ), + public=False, + permission_classes=(IsAdminOrSwaggerToken,), +) + +# Swagger URL patterns +swagger_urlpatterns = [ + path('swagger-auth/', SwaggerTokenAuthView.as_view(), name='swagger-token-auth'), + path('swagger-auth/clear/', clear_swagger_auth, name='clear-swagger-auth'), + re_path(r'^swagger(?P\.json|\.yaml)$', + swagger_auth_required(schema_view.without_ui(cache_timeout=0)), + name='schema-json'), + path('swagger/', CustomSwaggerView.as_view(), name='schema-swagger-ui'), + re_path(r'^redoc/$', + swagger_auth_required(schema_view.with_ui('redoc', cache_timeout=0)), + name='schema-redoc'), +] + +urlpatterns = [ + # Root redirect to admin + path("", HomeView.as_view(), name="home"), + path("i18n/", include("django.conf.urls.i18n")), + path('filer/', include('filer.urls')), +] + +# i18n admin + swagger patterns +urlpatterns += i18n_patterns( + path("imam-javad/admin/", project_admin_site.urls), + path("dovoodi/admin/", dovoodi_admin_site.urls), + path('docs/', CustomAPIDocumentationView.as_view(), name='docs-index'), + *swagger_urlpatterns, + path('filer/', include('filer.urls')), +) + +if settings.DEBUG: + urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)