diff --git a/apps/users/admin.py b/apps/users/admin.py index f6b3f68..201ebf7 100644 --- a/apps/users/admin.py +++ b/apps/users/admin.py @@ -1,3 +1,5 @@ +# apps/users/admin.py + from django.contrib import admin from .models import User diff --git a/egypt_metro/context_processors.py b/egypt_metro/context_processors.py new file mode 100644 index 0000000..54dd1ae --- /dev/null +++ b/egypt_metro/context_processors.py @@ -0,0 +1,7 @@ +# egypt_metro/context_processors.py + +# Context processors are functions that run before rendering a template. +def project_name(request): + return { + 'project_name': 'Egypt Metro', # Project name + } diff --git a/egypt_metro/settings.py b/egypt_metro/settings.py index a9c49fa..76cb987 100644 --- a/egypt_metro/settings.py +++ b/egypt_metro/settings.py @@ -10,35 +10,29 @@ https://docs.djangoproject.com/en/5.1/ref/settings/ """ +# import logging from pathlib import Path # File path helper import os # Operating system dependent functionality import dj_database_url # type: ignore # Parse database URLs from dotenv import load_dotenv # Load environment variables from .env file from datetime import timedelta # Time delta for JWT tokens from corsheaders.defaults import default_headers # Default headers for CORS -# from decouple import config +from decouple import config from datetime import datetime # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent # Base directory for the project +PROJECT_NAME = "Egypt Metro" # Load the appropriate .env file based on an environment variable ENVIRONMENT = os.getenv("ENVIRONMENT", "dev") # Default to dev dotenv_path = BASE_DIR / f"env/.env.{ENVIRONMENT}" load_dotenv(dotenv_path) -# Check if the .env file exists and load it -# if dotenv_path.is_file(): -# load_dotenv(dotenv_path) -# else: -# raise FileNotFoundError(f"Environment file not found: {dotenv_path}") - # SECURITY WARNING: keep the secret key used in production secret! SECRET_KEY = os.getenv("SECRET_KEY") # Secret key for Django -# DEBUG = os.getenv("DEBUG", "False") == "True" # Default to False -# ALLOWED_HOSTS = config("ALLOWED_HOSTS", default="").split(",") -DEBUG = True -ALLOWED_HOSTS = ["*"] # Temporary for debugging purposes +DEBUG = os.getenv("DEBUG", "False") == "True" # Default to False +ALLOWED_HOSTS = config("ALLOWED_HOSTS", default="").split(",") # Set API start time to the application's boot time API_START_TIME = datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S") @@ -47,8 +41,11 @@ SESSION_SAVE_EVERY_REQUEST = True # Extend session each request SESSION_EXPIRE_AT_BROWSER_CLOSE = False # Session persists after closing the browser -# Application definition +# Debugging: Log the environment +# logger = logging.getLogger(__name__) +# logger.debug(f"Current environment: {ENVIRONMENT}") +# Application definition INSTALLED_APPS = [ "django.contrib.admin", # Admin panel "django.contrib.auth", # Authentication framework @@ -73,6 +70,7 @@ "apps.stations.apps.StationsConfig", # Stations app ] +# Middleware configuration MIDDLEWARE = [ "django.middleware.security.SecurityMiddleware", # Security middleware "whitenoise.middleware.WhiteNoiseMiddleware", # WhiteNoise middleware @@ -109,7 +107,13 @@ CORS_ALLOW_CREDENTIALS = True # Allow credentials if ENVIRONMENT == "dev": - CORS_ALLOW_ALL_ORIGINS = True + INSTALLED_APPS += ["silk"] + MIDDLEWARE += ["silk.middleware.SilkyMiddleware"] + + SILKY_PYTHON_PROFILER = True # Enables Python code profiling + SILKY_MAX_REQUESTS = 1000 # Limit the number of requests to profile + SILKY_RECORD_SQL = True # Records SQL queries + SILKY_AUTHENTICATION = True # Protect Silk interface with authentication TEMPLATES = [ { @@ -122,8 +126,8 @@ "django.template.context_processors.request", # Request context processor "django.contrib.auth.context_processors.auth", # Auth context processor "django.contrib.messages.context_processors.messages", # Messages context processor - "django.template.context_processors.request", # Request context processor 'django.template.context_processors.static', # Static context processor + 'egypt_metro.context_processors.project_name', ], }, }, @@ -135,10 +139,6 @@ # Custom User Model AUTH_USER_MODEL = "users.User" -# Load secret file if in production -# if ENVIRONMENT == "prod": -# load_dotenv("/etc/secrets/env.prod") # Load production secrets - # General settings SECRET_KEY = os.getenv("SECRET_KEY") # Secret key for Django BASE_URL = os.getenv("BASE_URL") # Base URL for the project @@ -176,42 +176,38 @@ "sslmode": "require", # Enforce SSL for secure connections }) + SESSION_COOKIE_SECURE = True # Ensures cookies are only sent over HTTPS + CSRF_COOKIE_SECURE = True # CSRF cookie should also be secure + SESSION_EXPIRE_AT_BROWSER_CLOSE = False # Keep sessions open until explicitly logged out + SESSION_COOKIE_HTTPONLY = True # Avoid client-side access to session cookie + SESSION_SAVE_EVERY_REQUEST = True # Save the session on every request to ensure data consistency + CSRF_COOKIE_HTTPONLY = True # Make sure CSRF cookie can't be accessed via JavaScript + CSRF_TRUSTED_ORIGINS = [ + 'https://backend-54v5.onrender.com/', # Replace with your actual domain + ] + + # For secure connections over HTTPS (especially for production) + SECURE_SSL_REDIRECT = True # Redirect HTTP to HTTPS + SECURE_HSTS_SECONDS = 3600 # HTTP Strict Transport Security (HSTS) in seconds + SECURE_HSTS_INCLUDE_SUBDOMAINS = True + SECURE_HSTS_PRELOAD = True + REQUIRED_ENV_VARS = ["SECRET_KEY", "DATABASE_URL", "JWT_SECRET", "BASE_URL"] for var in REQUIRED_ENV_VARS: if not os.getenv(var): raise ValueError(f"{var} is not set in environment variables.") -if ENVIRONMENT == "prod": - CACHES = { - "default": { - "BACKEND": "django.core.cache.backends.redis.RedisCache", - "LOCATION": "redis://127.0.0.1:6379/1", - } - } - SECURE_SSL_REDIRECT = True - SECURE_HSTS_SECONDS = 31536000 - SECURE_HSTS_PRELOAD = True - SECURE_HSTS_INCLUDE_SUBDOMAINS = True - CSRF_COOKIE_SECURE = True - SESSION_COOKIE_SECURE = True -else: - SECURE_SSL_REDIRECT = False - SECURE_HSTS_PRELOAD = False - SECURE_HSTS_INCLUDE_SUBDOMAINS = False - CSRF_COOKIE_SECURE = False - SESSION_COOKIE_SECURE = False - - -# if not DEBUG: # Enable only in production -# SECURE_SSL_REDIRECT = os.getenv("SECURE_SSL_REDIRECT", "True") == "True" -# SECURE_HSTS_SECONDS = int(os.getenv("SECURE_HSTS_SECONDS", "31536000")) -# SECURE_HSTS_PRELOAD = os.getenv("SECURE_HSTS_PRELOAD", "True") == "True" -# SECURE_HSTS_INCLUDE_SUBDOMAINS = ( -# os.getenv("SECURE_HSTS_INCLUDE_SUBDOMAINS", "True") == "True" -# ) -# CSRF_COOKIE_SECURE = True -# SESSION_COOKIE_SECURE = True +# if ENVIRONMENT == "prod": +# CACHES = { +# "default": { +# "BACKEND": "django.core.cache.backends.redis.RedisCache", +# "LOCATION": "redis://127.0.0.1:6379/1", +# 'OPTIONS': { +# 'CLIENT_CLASS': 'django_redis.client.DefaultClient' +# } +# } +# } # Password validation # https://docs.djangoproject.com/en/5.1/ref/settings/#auth-password-validators @@ -236,12 +232,6 @@ # "allauth.account.auth_backends.AuthenticationBackend", # For allauth ] -# SOCIALACCOUNT_PROVIDERS = { -# "google": { -# "APP": {"client_id": "your-client-id", "secret": "your-secret", "key": ""} -# } -# } - REST_FRAMEWORK = { "DEFAULT_AUTHENTICATION_CLASSES": ( "rest_framework_simplejwt.authentication.JWTAuthentication", @@ -317,15 +307,17 @@ }, } +# Cache configuration CACHES = { "default": { "BACKEND": "django.core.cache.backends.locmem.LocMemCache", # Local memory cache - "LOCATION": "unique-snowflake", + "LOCATION": "unique-snowflake", # Unique identifier for the cache } } -SESSION_ENGINE = "django.contrib.sessions.backends.cache" -SESSION_CACHE_ALIAS = "default" +# Session engine configuration +SESSION_ENGINE = "django.contrib.sessions.backends.cache" # Session engine +SESSION_CACHE_ALIAS = "default" # Cache alias for sessions INTERNAL_IPS = [ "127.0.0.1", # Localhost @@ -357,21 +349,6 @@ }, } -# Initialize Sentry for Error Tracking -# SENTRY_DSN = os.getenv("SENTRY_DSN") # Use environment variable - -# if SENTRY_DSN: -# import sentry_sdk # type: ignore -# from sentry_sdk.integrations.django import DjangoIntegration # type: ignore - -# sentry_sdk.init( -# dsn=SENTRY_DSN, -# integrations=[DjangoIntegration()], -# send_default_pii=True, -# ) -# else: -# print("Sentry DSN not configured. Skipping Sentry initialization.") - # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/5.1/howto/deployment/checklist/ diff --git a/egypt_metro/urls.py b/egypt_metro/urls.py index f3e05aa..a2e5831 100644 --- a/egypt_metro/urls.py +++ b/egypt_metro/urls.py @@ -14,10 +14,12 @@ 1. Import the include() function: from django.urls import include, path 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) """ +# egypt_metro/urls.py + import logging from django.contrib import admin from django.urls import path, include -# from egypt_metro import settings +from egypt_metro import settings from .views import health_check, home # from django.conf.urls.static import static from drf_yasg.views import get_schema_view @@ -31,7 +33,7 @@ # OpenAPI schema view schema_view = get_schema_view( openapi.Info( - title="Metro API", + title=f"{settings.PROJECT_NAME} API", default_version="v1", description="API documentation for Metro application", terms_of_service="https://www.google.com/policies/terms/", @@ -53,6 +55,9 @@ # Home path("", home, name="home"), # Home view + # Check environment + # path('check-environment/', check_environment, name='check_environment'), + # Authentication path("accounts/", include("allauth.urls")), # Allauth authentication @@ -73,6 +78,9 @@ ), # Swagger UI path("redoc/", schema_view.with_ui("redoc", cache_timeout=0), name="schema-redoc"), # ReDoc + + # Silk Profiling + path("silk/", include("silk.urls", namespace="silk")) if settings.ENVIRONMENT == "dev" else None, ] # Debug the URL loading process diff --git a/egypt_metro/views.py b/egypt_metro/views.py index cd89438..58ba97c 100644 --- a/egypt_metro/views.py +++ b/egypt_metro/views.py @@ -1,4 +1,7 @@ +# egypt_metro/views.py + import logging +import os from django.http import JsonResponse from django.db import connection # from django.utils.timezone import now @@ -39,21 +42,20 @@ def home(request): hours, remainder = divmod(remainder, 3600) # 3600 seconds in an hour minutes, seconds = divmod(remainder, 60) # 60 seconds in a minute - # Format uptime - uptime = f"{int(days)} days, {int(hours)} hours, {int(minutes)} minutes, {int(seconds)} seconds" + # Get the current environment (dev or prod) + environment = os.getenv("ENVIRONMENT", "dev") # Default to dev if not set + logger.debug(f"Current environment: {environment}") # Data to return as JSON response data = { - "message": "Welcome to Egypt Metro Backend", - "status": "OK", # Status indicating the API is operational "admin_panel": "/admin/", # Link to Django admin panel "api_documentation": "/docs/", # Link to API documentation "health_check": "/health/", # Health check endpoint "swagger": "/swagger/", # Swagger API documentation "redoc": "/redoc/", # Redoc API documentation "version": "1.0.0", # Backend version - "uptime": uptime, # Dynamically calculated uptime "current_date_time": current_date_time, # Current date and time with minutes and seconds + "environment": environment, # Current environment (dev or prod) "api_routes": { "users": "/api/users/", # User-related routes "register": "/api/users/register/", # User registration @@ -77,10 +79,9 @@ def home(request):

Welcome to Egypt Metro Backend

-

Status: {data['status']}

Version: {data['version']}

-

Uptime: {data['uptime']}

-

Current Date & Time: {data['current_date_time']}

+

Date & Time: {data['current_date_time']}

+

Environment: {data['environment']}

Quick Links