This commit is contained in:
Iliyan Angelov
2025-11-24 16:47:37 +02:00
parent d7ff5c71e6
commit 0b1cabcfaf
45 changed files with 2021 additions and 28 deletions

39
backEnd/.dockerignore Normal file
View File

@@ -0,0 +1,39 @@
__pycache__
*.pyc
*.pyo
*.pyd
.Python
*.so
*.egg
*.egg-info
dist
build
.venv
venv/
env/
ENV/
.env
.venv
*.log
logs/
*.db
*.sqlite3
db.sqlite3
.git
.gitignore
README.md
*.md
.DS_Store
.vscode
.idea
*.swp
*.swo
*~
.pytest_cache
.coverage
htmlcov/
.tox/
.mypy_cache/
.dmypy.json
dmypy.json

View File

@@ -4,6 +4,10 @@ SECRET_KEY=ks68*5@of1l&4rn1imsqdk9$khcya!&a#jtd89f!v^qg1w0&hc
DEBUG=True
ALLOWED_HOSTS=localhost,127.0.0.1
INTERNAL_API_KEY=your-generated-key-here
PRODUCTION_ORIGINS=https://gnxsoft.com,https://www.gnxsoft.com
CSRF_TRUSTED_ORIGINS=https://gnxsoft.com,https://www.gnxsoft.com
# Email Configuration (Development - uses console backend by default)
USE_SMTP_IN_DEV=True
DEFAULT_FROM_EMAIL=support@gnxsoft.com

36
backEnd/Dockerfile Normal file
View File

@@ -0,0 +1,36 @@
# Django Backend Dockerfile
FROM python:3.12-slim
# Set environment variables
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
DEBIAN_FRONTEND=noninteractive
# Set work directory
WORKDIR /app
# Install system dependencies
RUN apt-get update && apt-get install -y \
gcc \
postgresql-client \
&& rm -rf /var/lib/apt/lists/*
# Install Python dependencies
COPY requirements.txt /app/
RUN pip install --no-cache-dir -r requirements.txt
# Copy project
COPY . /app/
# Create directories for media and static files
RUN mkdir -p /app/media /app/staticfiles /app/logs
# Collect static files (will be done at runtime if needed)
# RUN python manage.py collectstatic --noinput
# Expose port
EXPOSE 1086
# Run gunicorn
CMD ["gunicorn", "--bind", "0.0.0.0:1086", "--workers", "3", "--timeout", "120", "--access-logfile", "-", "--error-logfile", "-", "gnx.wsgi:application"]

Binary file not shown.

View File

@@ -0,0 +1,2 @@
# Django management package

View File

@@ -0,0 +1,2 @@
# Django management commands package

View File

@@ -0,0 +1,34 @@
"""
Management command to display the current INTERNAL_API_KEY
Useful for copying to nginx configuration
"""
from django.core.management.base import BaseCommand
from django.conf import settings
class Command(BaseCommand):
help = 'Display the current INTERNAL_API_KEY for nginx configuration'
def handle(self, *args, **options):
api_key = getattr(settings, 'INTERNAL_API_KEY', None)
if not api_key:
self.stdout.write(
self.style.ERROR('❌ INTERNAL_API_KEY is not set!')
)
self.stdout.write(
' Set it in your .env file or it will be auto-generated in DEBUG mode.'
)
return
self.stdout.write(self.style.SUCCESS('\n✓ Current INTERNAL_API_KEY:'))
self.stdout.write(self.style.WARNING(f'\n{api_key}\n'))
self.stdout.write('📋 Copy this key to your nginx configuration:')
self.stdout.write(' In nginx.conf, set:')
self.stdout.write(f' set $api_key "{api_key}";')
self.stdout.write('')
self.stdout.write(' Or add to your .env file:')
self.stdout.write(f' INTERNAL_API_KEY={api_key}')
self.stdout.write('')

View File

@@ -0,0 +1,99 @@
"""
Management command to update admin user credentials
"""
from django.core.management.base import BaseCommand
from django.contrib.auth import get_user_model
from django.db import transaction
User = get_user_model()
class Command(BaseCommand):
help = 'Update admin user username and password'
def add_arguments(self, parser):
parser.add_argument(
'--username',
type=str,
default='gnx',
help='Admin username (default: gnx)'
)
parser.add_argument(
'--password',
type=str,
default='P4eli240453',
help='Admin password (default: P4eli240453)'
)
def handle(self, *args, **options):
username = options['username']
password = options['password']
try:
with transaction.atomic():
# Try to find existing user with this username
user = User.objects.filter(username=username).first()
if user:
# Update existing user
user.set_password(password)
user.is_staff = True
user.is_superuser = True
user.is_active = True
user.save()
self.stdout.write(
self.style.SUCCESS(
f'✓ Successfully updated admin user "{username}"'
)
)
else:
# Check if there are any existing superusers
existing_superusers = User.objects.filter(is_superuser=True)
if existing_superusers.exists():
# Update the first superuser
user = existing_superusers.first()
old_username = user.username
user.username = username
user.set_password(password)
user.is_staff = True
user.is_superuser = True
user.is_active = True
user.save()
self.stdout.write(
self.style.SUCCESS(
f'✓ Successfully updated admin user from "{old_username}" to "{username}"'
)
)
else:
# Create new superuser
user = User.objects.create_user(
username=username,
password=password,
is_staff=True,
is_superuser=True,
is_active=True
)
self.stdout.write(
self.style.SUCCESS(
f'✓ Successfully created admin user "{username}"'
)
)
self.stdout.write(
self.style.SUCCESS(
f'\nAdmin credentials:\n'
f' Username: {username}\n'
f' Password: {password}\n'
f' Is Staff: {user.is_staff}\n'
f' Is Superuser: {user.is_superuser}\n'
f' Is Active: {user.is_active}\n'
)
)
except Exception as e:
self.stdout.write(
self.style.ERROR(f'❌ Error updating admin user: {str(e)}')
)
raise

View File

@@ -0,0 +1,132 @@
"""
Admin IP Restriction Middleware
Restricts Django admin access to specific IP addresses only
"""
from django.http import HttpResponseForbidden
from django.conf import settings
from django.urls import resolve
import ipaddress
import logging
logger = logging.getLogger('django.security')
class AdminIPRestrictionMiddleware:
"""
Restricts Django admin access to whitelisted IP addresses only.
This provides an additional layer of security for the admin panel.
"""
def __init__(self, get_response):
self.get_response = get_response
# Get allowed admin IPs from settings
# Default to the user's IP if not specified
admin_ips = getattr(settings, 'ADMIN_ALLOWED_IPS', ['193.194.155.249'])
# Convert to list if it's a string
if isinstance(admin_ips, str):
admin_ips = [ip.strip() for ip in admin_ips.split(',') if ip.strip()]
self.allowed_ips = []
for ip_str in admin_ips:
try:
# Support both single IPs and CIDR notation
if '/' in ip_str:
self.allowed_ips.append(ipaddress.ip_network(ip_str, strict=False))
else:
self.allowed_ips.append(ipaddress.ip_address(ip_str))
except ValueError:
logger.warning(f"Invalid IP address in ADMIN_ALLOWED_IPS: {ip_str}")
# Also allow localhost for development
self.allowed_ips.extend([
ipaddress.ip_address('127.0.0.1'),
ipaddress.ip_address('::1'),
])
def get_client_ip(self, request):
"""
Get the real client IP address, handling proxy headers
"""
# Check for forwarded IP (from proxy/load balancer)
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
if x_forwarded_for:
# Get the first IP in the chain (original client)
ip = x_forwarded_for.split(',')[0].strip()
return ip
# Check for real IP header (some proxies use this)
x_real_ip = request.META.get('HTTP_X_REAL_IP')
if x_real_ip:
return x_real_ip.strip()
# Fall back to REMOTE_ADDR
return request.META.get('REMOTE_ADDR', '')
def is_admin_path(self, request):
"""
Check if the request is for the Django admin
"""
path = request.path
return path.startswith('/admin/')
def is_ip_allowed(self, client_ip):
"""
Check if the client IP is in the allowed list
"""
try:
client_ip_obj = ipaddress.ip_address(client_ip)
# Check if IP matches any allowed IP or network
for allowed in self.allowed_ips:
if isinstance(allowed, ipaddress.IPv4Address) or isinstance(allowed, ipaddress.IPv6Address):
# Direct IP match
if client_ip_obj == allowed:
return True
elif isinstance(allowed, ipaddress.IPv4Network) or isinstance(allowed, ipaddress.IPv6Network):
# Network/CIDR match
if client_ip_obj in allowed:
return True
return False
except ValueError:
logger.error(f"Invalid IP address format: {client_ip}")
return False
def __call__(self, request):
# Only check admin paths
if not self.is_admin_path(request):
return self.get_response(request)
# In DEBUG mode, you might want to allow all (optional)
# Uncomment the next 3 lines if you want to disable IP restriction in DEBUG mode
# if settings.DEBUG:
# return self.get_response(request)
# Get client IP
client_ip = self.get_client_ip(request)
if not client_ip:
logger.warning("Could not determine client IP for admin access attempt")
return HttpResponseForbidden(
"<h1>Access Denied</h1>"
"<p>Unable to verify your IP address. Admin access is restricted.</p>"
)
# Check if IP is allowed
if not self.is_ip_allowed(client_ip):
logger.warning(
f"Blocked admin access attempt from IP: {client_ip} "
f"to path: {request.path}"
)
return HttpResponseForbidden(
"<h1>Access Denied</h1>"
"<p>Admin access is restricted to authorized IP addresses only.</p>"
f"<p>Your IP: {client_ip}</p>"
)
# IP is allowed, continue
return self.get_response(request)

View File

@@ -12,6 +12,8 @@ https://docs.djangoproject.com/en/4.2/ref/settings/
from pathlib import Path
import os
import secrets
import warnings
from decouple import config
# Build paths inside the project like this: BASE_DIR / 'subdir'.
@@ -62,6 +64,7 @@ MIDDLEWARE = [
'corsheaders.middleware.CorsMiddleware',
'django.middleware.security.SecurityMiddleware',
'gnx.middleware.ip_whitelist.IPWhitelistMiddleware', # Production: Block external access
'gnx.middleware.admin_ip_restriction.AdminIPRestrictionMiddleware', # Restrict admin to specific IPs
'gnx.middleware.api_security.FrontendAPIProxyMiddleware', # Validate requests from frontend/nginx
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
@@ -95,6 +98,16 @@ WSGI_APPLICATION = 'gnx.wsgi.application'
# Database
# https://docs.djangoproject.com/en/4.2/ref/settings/#databases
# Support both PostgreSQL (production) and SQLite (development)
DATABASE_URL = config('DATABASE_URL', default='')
if DATABASE_URL and DATABASE_URL.startswith('postgresql://'):
# PostgreSQL configuration
import dj_database_url
DATABASES = {
'default': dj_database_url.parse(DATABASE_URL, conn_max_age=600)
}
else:
# SQLite configuration (development/fallback)
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
@@ -162,6 +175,10 @@ INTERNAL_IPS = ['127.0.0.1', '::1']
# Custom allowed IPs for IP whitelist middleware (comma-separated)
CUSTOM_ALLOWED_IPS = config('CUSTOM_ALLOWED_IPS', default='', cast=lambda v: [s.strip() for s in v.split(',') if s.strip()])
# Admin IP Restriction - Only these IPs can access Django admin
# Comma-separated list of IP addresses or CIDR networks
ADMIN_ALLOWED_IPS = config('ADMIN_ALLOWED_IPS', default='193.194.155.249', cast=lambda v: [s.strip() for s in v.split(',') if s.strip()])
# Internationalization
# https://docs.djangoproject.com/en/4.2/topics/i18n/
@@ -236,8 +253,28 @@ REST_FRAMEWORK = {
# ============================================================================
# Internal API Key - nginx will add this header to prove request came through proxy
# Generate a strong random key: python -c "import secrets; print(secrets.token_urlsafe(32))"
INTERNAL_API_KEY = config('INTERNAL_API_KEY', default='' if not DEBUG else 'dev-key-change-in-production')
# Auto-generates a secure key if not provided (only in DEBUG mode for development)
# In production, you MUST set INTERNAL_API_KEY in your .env file
_manual_api_key = config('INTERNAL_API_KEY', default='')
if not _manual_api_key:
if DEBUG:
# Auto-generate a secure key for development
_auto_generated_key = secrets.token_urlsafe(32)
INTERNAL_API_KEY = _auto_generated_key
warnings.warn(
f"⚠️ INTERNAL_API_KEY not set. Auto-generated key for development: {_auto_generated_key}\n"
f" Add this to your nginx config and .env file for consistency.\n"
f" In production, you MUST set INTERNAL_API_KEY explicitly in .env",
UserWarning
)
else:
# Production requires explicit key
raise ValueError(
"INTERNAL_API_KEY must be set in production. "
"Generate one with: python -c \"import secrets; print(secrets.token_urlsafe(32))\""
)
else:
INTERNAL_API_KEY = _manual_api_key
# API security path pattern (default: all /api/ paths)
API_SECURITY_PATH_PATTERN = config('API_SECURITY_PATH_PATTERN', default=r'^/api/')

View File

@@ -32877,3 +32877,307 @@ INFO 2025-11-24 06:40:57,132 autoreload 139932 123776047718528 /home/gnx/Desktop
INFO 2025-11-24 06:40:57,712 autoreload 140119 136966227431552 Watching for file changes with StatReloader
INFO 2025-11-24 06:41:38,174 autoreload 140119 136966227431552 /home/gnx/Desktop/GNX-WEB/backEnd/gnx/settings.py changed, reloading.
INFO 2025-11-24 06:41:38,716 autoreload 140239 133780171337856 Watching for file changes with StatReloader
INFO 2025-11-24 13:09:15,381 autoreload 11176 128073016684672 Watching for file changes with StatReloader
INFO 2025-11-24 13:10:34,723 basehttp 11176 128072933168832 "OPTIONS /api/case-studies/?ordering=display_order&page_size=5 HTTP/1.1" 200 0
INFO 2025-11-24 13:10:34,726 basehttp 11176 128072933168832 "OPTIONS /api/career/jobs HTTP/1.1" 200 0
INFO 2025-11-24 13:10:34,726 basehttp 11176 128072949954240 "OPTIONS /api/home/banner/ HTTP/1.1" 200 0
INFO 2025-11-24 13:10:34,725 basehttp 11176 128072941561536 "OPTIONS /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 0
INFO 2025-11-24 13:10:34,730 basehttp 11176 128072924776128 "OPTIONS /api/blog/posts/latest/?limit=12 HTTP/1.1" 200 0
INFO 2025-11-24 13:10:34,775 basehttp 11176 128072949954240 "GET /api/home/banner/ HTTP/1.1" 200 3438
INFO 2025-11-24 13:10:34,779 basehttp 11176 128072916383424 "GET /api/case-studies/?ordering=display_order&page_size=5 HTTP/1.1" 200 4744
INFO 2025-11-24 13:10:34,792 basehttp 11176 128072941561536 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 13:10:34,826 basehttp 11176 128072933168832 "GET /api/career/jobs HTTP/1.1" 200 4675
INFO 2025-11-24 13:10:34,863 basehttp 11176 128072941561536 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 13:10:34,871 basehttp 11176 128072924776128 "GET /api/blog/posts/latest/?limit=12 HTTP/1.1" 200 8374
INFO 2025-11-24 13:10:34,919 basehttp 11176 128072941561536 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 13:11:04,757 basehttp 11176 128072941561536 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 13:11:04,757 basehttp 11176 128072924776128 "OPTIONS /api/blog/categories/ HTTP/1.1" 200 0
INFO 2025-11-24 13:11:04,757 basehttp 11176 128072916383424 "OPTIONS /api/blog/posts/?page=1&page_size=6 HTTP/1.1" 200 0
INFO 2025-11-24 13:11:04,773 basehttp 11176 128072933168832 "GET /api/career/jobs HTTP/1.1" 200 4675
INFO 2025-11-24 13:11:04,796 basehttp 11176 128072916383424 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 13:11:04,803 basehttp 11176 128072924776128 "GET /api/blog/categories/ HTTP/1.1" 200 1418
INFO 2025-11-24 13:11:04,809 basehttp 11176 128072933168832 "GET /api/career/jobs HTTP/1.1" 200 4675
INFO 2025-11-24 13:11:04,821 basehttp 11176 128072941561536 "GET /api/blog/posts/?page=1&page_size=6 HTTP/1.1" 200 5121
INFO 2025-11-24 13:11:04,852 basehttp 11176 128072949954240 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 13:11:04,859 basehttp 11176 128072916383424 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 13:11:04,875 basehttp 11176 128072924776128 "GET /api/blog/categories/ HTTP/1.1" 200 1418
INFO 2025-11-24 13:11:04,889 basehttp 11176 128072949954240 "GET /api/blog/posts/?page=1&page_size=6 HTTP/1.1" 200 5121
INFO 2025-11-24 13:11:09,343 basehttp 11176 128072569321152 "GET /api/blog/posts/best-practices-for-building-scalable-enterprise-apis/ HTTP/1.1" 200 4445
INFO 2025-11-24 13:11:09,514 basehttp 11176 128072924776128 "OPTIONS /api/blog/posts/latest/?limit=8 HTTP/1.1" 200 0
INFO 2025-11-24 13:11:09,514 basehttp 11176 128072949954240 "OPTIONS /api/blog/posts/best-practices-for-building-scalable-enterprise-apis/ HTTP/1.1" 200 0
INFO 2025-11-24 13:11:09,533 basehttp 11176 128072941561536 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 13:11:09,543 basehttp 11176 128072916383424 "GET /api/career/jobs HTTP/1.1" 200 4675
INFO 2025-11-24 13:11:09,592 basehttp 11176 128072941561536 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 13:11:09,592 basehttp 11176 128072949954240 "GET /api/blog/posts/best-practices-for-building-scalable-enterprise-apis/ HTTP/1.1" 200 4445
INFO 2025-11-24 13:11:09,602 basehttp 11176 128072916383424 "GET /api/career/jobs HTTP/1.1" 200 4675
INFO 2025-11-24 13:11:09,611 basehttp 11176 128072924776128 "GET /api/blog/posts/latest/?limit=8 HTTP/1.1" 200 6678
INFO 2025-11-24 13:11:09,652 basehttp 11176 128072941561536 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 13:11:09,672 basehttp 11176 128072949954240 "GET /api/blog/posts/best-practices-for-building-scalable-enterprise-apis/ HTTP/1.1" 200 4445
INFO 2025-11-24 13:11:09,684 basehttp 11176 128072916383424 "GET /api/blog/posts/latest/?limit=8 HTTP/1.1" 200 6678
INFO 2025-11-24 13:11:09,703 basehttp 11176 128072941561536 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 13:11:18,799 basehttp 11176 128072949954240 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 13:11:18,811 basehttp 11176 128072916383424 "GET /api/career/jobs HTTP/1.1" 200 4675
INFO 2025-11-24 13:11:18,819 basehttp 11176 128072941561536 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 13:11:18,831 basehttp 11176 128072949954240 "GET /api/career/jobs HTTP/1.1" 200 4675
INFO 2025-11-24 13:11:18,836 basehttp 11176 128072916383424 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 13:11:18,887 basehttp 11176 128072941561536 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 13:12:18,200 basehttp 11176 128072916383424 "GET /api/home/banner/ HTTP/1.1" 200 3438
INFO 2025-11-24 13:12:18,208 basehttp 11176 128072949954240 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 13:12:18,221 basehttp 11176 128072941561536 "GET /api/case-studies/?ordering=display_order&page_size=5 HTTP/1.1" 200 4744
INFO 2025-11-24 13:12:18,221 basehttp 11176 128072933168832 "GET /api/career/jobs HTTP/1.1" 200 4675
INFO 2025-11-24 13:12:18,251 basehttp 11176 128072916383424 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 13:12:18,259 basehttp 11176 128072949954240 "GET /api/home/banner/ HTTP/1.1" 200 3438
INFO 2025-11-24 13:12:18,267 basehttp 11176 128072933168832 "GET /api/career/jobs HTTP/1.1" 200 4675
INFO 2025-11-24 13:12:18,274 basehttp 11176 128072941561536 "GET /api/case-studies/?ordering=display_order&page_size=5 HTTP/1.1" 200 4744
INFO 2025-11-24 13:12:18,288 basehttp 11176 128072924776128 "GET /api/blog/posts/latest/?limit=12 HTTP/1.1" 200 8374
INFO 2025-11-24 13:12:18,304 basehttp 11176 128072916383424 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 13:12:18,334 basehttp 11176 128072924776128 "GET /api/blog/posts/latest/?limit=12 HTTP/1.1" 200 8374
INFO 2025-11-24 13:12:18,365 basehttp 11176 128072949954240 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 13:12:18,378 basehttp 11176 128072941561536 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 13:12:18,394 basehttp 11176 128072916383424 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 13:12:58,727 autoreload 11176 128073016684672 /home/gnx/Desktop/GNX-WEB/backEnd/gnx/settings.py changed, reloading.
INFO 2025-11-24 13:12:59,333 autoreload 13550 127591779987584 Watching for file changes with StatReloader
INFO 2025-11-24 13:13:07,973 autoreload 13550 127591779987584 /home/gnx/Desktop/GNX-WEB/backEnd/gnx/settings.py changed, reloading.
INFO 2025-11-24 13:13:08,571 autoreload 13656 125806206767232 Watching for file changes with StatReloader
INFO 2025-11-24 13:13:13,119 autoreload 13656 125806206767232 /home/gnx/Desktop/GNX-WEB/backEnd/gnx/settings.py changed, reloading.
INFO 2025-11-24 13:13:13,738 autoreload 13700 126054542864512 Watching for file changes with StatReloader
INFO 2025-11-24 13:13:53,121 autoreload 13700 126054542864512 /home/gnx/Desktop/GNX-WEB/backEnd/gnx/settings.py changed, reloading.
INFO 2025-11-24 13:13:53,738 autoreload 14178 134881899106432 Watching for file changes with StatReloader
INFO 2025-11-24 13:15:05,124 autoreload 14521 127272466882688 Watching for file changes with StatReloader
INFO 2025-11-24 13:15:28,205 basehttp 14521 127272391534272 "OPTIONS /api/about/page/ HTTP/1.1" 200 0
INFO 2025-11-24 13:15:28,216 basehttp 14521 127272399926976 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 13:15:28,234 basehttp 14521 127272383141568 "GET /api/career/jobs HTTP/1.1" 200 4675
INFO 2025-11-24 13:15:28,248 basehttp 14521 127272399926976 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 13:15:28,263 basehttp 14521 127272374748864 "GET /api/about/page/ HTTP/1.1" 200 8597
INFO 2025-11-24 13:15:28,270 basehttp 14521 127272383141568 "GET /api/career/jobs HTTP/1.1" 200 4675
INFO 2025-11-24 13:15:28,286 basehttp 14521 127272374748864 "GET /api/about/page/ HTTP/1.1" 200 8597
INFO 2025-11-24 13:15:28,305 basehttp 14521 127272391534272 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 13:15:28,316 basehttp 14521 127272399926976 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 13:15:28,373 basehttp 14521 127272391534272 "GET /api/about/page/ HTTP/1.1" 200 8597
INFO 2025-11-24 13:15:28,465 basehttp 14521 127272399926976 "GET /api/about/page/ HTTP/1.1" 200 8597
INFO 2025-11-24 13:15:28,466 basehttp 14521 127272391534272 "GET /api/about/page/ HTTP/1.1" 200 8597
INFO 2025-11-24 13:15:28,485 basehttp 14521 127272399926976 "GET /api/about/page/ HTTP/1.1" 200 8597
INFO 2025-11-24 13:16:36,991 basehttp 14521 127272374748864 "GET /api/case-studies/?ordering=display_order&page_size=5 HTTP/1.1" 200 4744
INFO 2025-11-24 13:16:37,011 basehttp 14521 127272399926976 "GET /api/home/banner/ HTTP/1.1" 200 3438
INFO 2025-11-24 13:16:37,018 basehttp 14521 127272391534272 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 13:16:37,022 basehttp 14521 127272027682496 "GET /api/career/jobs HTTP/1.1" 200 4675
INFO 2025-11-24 13:16:37,050 basehttp 14521 127272399926976 "GET /api/home/banner/ HTTP/1.1" 200 3438
INFO 2025-11-24 13:16:37,058 basehttp 14521 127272374748864 "GET /api/case-studies/?ordering=display_order&page_size=5 HTTP/1.1" 200 4744
INFO 2025-11-24 13:16:37,076 basehttp 14521 127272391534272 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 13:16:37,082 basehttp 14521 127272027682496 "GET /api/career/jobs HTTP/1.1" 200 4675
INFO 2025-11-24 13:16:37,092 basehttp 14521 127272383141568 "GET /api/blog/posts/latest/?limit=12 HTTP/1.1" 200 8374
INFO 2025-11-24 13:16:37,141 basehttp 14521 127272374748864 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 13:16:37,150 basehttp 14521 127272391534272 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 13:16:37,155 basehttp 14521 127272399926976 "GET /api/blog/posts/latest/?limit=12 HTTP/1.1" 200 8374
INFO 2025-11-24 13:16:37,200 basehttp 14521 127272374748864 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 13:16:37,209 basehttp 14521 127272383141568 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 13:18:05,963 basehttp 14521 127272391534272 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 13:18:05,971 basehttp 14521 127272399926976 "GET /api/career/jobs HTTP/1.1" 200 4675
INFO 2025-11-24 13:18:05,983 basehttp 14521 127272383141568 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 13:18:05,993 basehttp 14521 127272374748864 "GET /api/career/jobs HTTP/1.1" 200 4675
INFO 2025-11-24 13:18:05,999 basehttp 14521 127272391534272 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 13:18:06,050 basehttp 14521 127272399926976 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 13:52:29,368 basehttp 14521 127272391534272 "GET /api/career/jobs HTTP/1.1" 200 4675
INFO 2025-11-24 13:52:29,375 basehttp 14521 127272383141568 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 13:52:29,396 basehttp 14521 127272383141568 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 13:58:17,380 basehttp 14521 127272383141568 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 13:58:48,661 basehttp 14521 127272383141568 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 13:58:52,344 basehttp 14521 127272383141568 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 13:59:44,445 basehttp 14521 127272383141568 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 14:00:29,872 basehttp 14521 127272383141568 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 14:00:58,873 basehttp 14521 127272383141568 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 14:01:17,080 basehttp 14521 127272383141568 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 14:01:37,625 basehttp 14521 127272383141568 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 14:01:59,686 basehttp 14521 127272383141568 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 14:02:11,931 basehttp 14521 127272383141568 "OPTIONS /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 0
INFO 2025-11-24 14:02:11,936 basehttp 14521 127272391534272 "OPTIONS /api/career/jobs HTTP/1.1" 200 0
INFO 2025-11-24 14:02:11,947 basehttp 14521 127272383141568 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 14:02:11,955 basehttp 14521 127272391534272 "GET /api/career/jobs HTTP/1.1" 200 4675
INFO 2025-11-24 14:02:12,106 basehttp 14521 127272383141568 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 14:02:32,343 basehttp 14521 127272383141568 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 14:02:52,316 basehttp 14521 127272383141568 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 14:03:46,939 basehttp 14521 127272383141568 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 14:03:59,826 basehttp 14521 127272383141568 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 14:04:24,979 basehttp 14521 127272383141568 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 14:05:10,040 basehttp 14521 127272383141568 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 14:06:12,564 basehttp 14521 127272383141568 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 14:06:25,176 basehttp 14521 127272383141568 "OPTIONS /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 0
INFO 2025-11-24 14:06:25,184 basehttp 14521 127272383141568 "OPTIONS /api/career/jobs HTTP/1.1" 200 0
INFO 2025-11-24 14:06:25,192 basehttp 14521 127272391534272 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 14:06:25,205 basehttp 14521 127272391534272 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 14:06:25,236 basehttp 14521 127272383141568 "GET /api/career/jobs HTTP/1.1" 200 4675
INFO 2025-11-24 14:06:45,565 basehttp 14521 127272391534272 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 14:07:01,911 basehttp 14521 127272383141568 "GET /api/career/jobs HTTP/1.1" 200 4675
INFO 2025-11-24 14:07:01,918 basehttp 14521 127272391534272 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 14:07:01,931 basehttp 14521 127272391534272 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 14:07:21,699 basehttp 14521 127272391534272 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 14:07:27,955 basehttp 14521 127272383141568 "OPTIONS /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 0
INFO 2025-11-24 14:07:27,965 basehttp 14521 127272391534272 "OPTIONS /api/career/jobs HTTP/1.1" 200 0
INFO 2025-11-24 14:07:27,965 basehttp 14521 127272383141568 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 14:07:27,974 basehttp 14521 127272391534272 "GET /api/career/jobs HTTP/1.1" 200 4675
INFO 2025-11-24 14:07:28,028 basehttp 14521 127272383141568 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 14:08:11,406 basehttp 14521 127272383141568 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 14:09:01,128 basehttp 14521 127272383141568 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 14:09:01,138 basehttp 14521 127272391534272 "GET /api/career/jobs HTTP/1.1" 200 4675
INFO 2025-11-24 14:09:01,146 basehttp 14521 127272383141568 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 14:09:16,649 basehttp 14521 127272391534272 "OPTIONS /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 0
INFO 2025-11-24 14:09:16,656 basehttp 14521 127272383141568 "OPTIONS /api/career/jobs HTTP/1.1" 200 0
INFO 2025-11-24 14:09:16,664 basehttp 14521 127272391534272 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 14:09:16,669 basehttp 14521 127272383141568 "GET /api/career/jobs HTTP/1.1" 200 4675
INFO 2025-11-24 14:09:16,726 basehttp 14521 127272391534272 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 14:09:34,382 basehttp 14521 127272383141568 "GET /api/career/jobs HTTP/1.1" 200 4675
INFO 2025-11-24 14:09:34,382 basehttp 14521 127272391534272 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 14:09:34,399 basehttp 14521 127272383141568 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 14:09:34,403 basehttp 14521 127272391534272 "GET /api/career/jobs HTTP/1.1" 200 4675
INFO 2025-11-24 14:09:34,460 basehttp 14521 127272383141568 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 14:09:34,469 basehttp 14521 127272391534272 "GET /api/career/jobs HTTP/1.1" 200 4675
INFO 2025-11-24 14:09:34,516 basehttp 14521 127272383141568 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 14:09:34,523 basehttp 14521 127272391534272 "GET /api/career/jobs HTTP/1.1" 200 4675
INFO 2025-11-24 14:09:49,006 basehttp 14521 127272383141568 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 14:09:49,018 basehttp 14521 127272391534272 "GET /api/career/jobs HTTP/1.1" 200 4675
INFO 2025-11-24 14:09:49,034 basehttp 14521 127272383141568 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 14:09:49,040 basehttp 14521 127272391534272 "GET /api/career/jobs HTTP/1.1" 200 4675
INFO 2025-11-24 14:09:49,093 basehttp 14521 127272383141568 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 14:09:49,150 basehttp 14521 127272391534272 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 14:10:47,794 basehttp 14521 127272383141568 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 14:10:48,854 basehttp 14521 127272391534272 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 14:10:54,869 basehttp 14521 127272383141568 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 14:10:59,880 basehttp 14521 127272391534272 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 14:11:14,155 basehttp 14521 127272391534272 "GET /api/career/jobs HTTP/1.1" 200 4675
INFO 2025-11-24 14:11:14,155 basehttp 14521 127272383141568 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 14:11:14,169 basehttp 14521 127272391534272 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 14:11:16,763 basehttp 14521 127272383141568 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 14:11:16,779 basehttp 14521 127272391534272 "GET /api/career/jobs HTTP/1.1" 200 4675
INFO 2025-11-24 14:11:16,786 basehttp 14521 127272383141568 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 14:11:22,210 basehttp 14521 127272391534272 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 14:11:22,216 basehttp 14521 127272383141568 "GET /api/career/jobs HTTP/1.1" 200 4675
INFO 2025-11-24 14:11:22,228 basehttp 14521 127272391534272 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 14:11:54,375 basehttp 14521 127272383141568 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 14:11:57,923 basehttp 14521 127272383141568 "OPTIONS /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 0
INFO 2025-11-24 14:11:57,936 basehttp 14521 127272383141568 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 14:12:12,466 basehttp 14521 127272399926976 "OPTIONS /api/career/jobs HTTP/1.1" 200 0
INFO 2025-11-24 14:12:12,474 basehttp 14521 127272383141568 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 14:12:12,484 basehttp 14521 127272374748864 "GET /api/career/jobs HTTP/1.1" 200 4675
INFO 2025-11-24 14:12:12,491 basehttp 14521 127272383141568 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 14:12:14,995 basehttp 14521 127272383141568 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 14:12:51,022 basehttp 14521 127272383141568 "OPTIONS /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 0
INFO 2025-11-24 14:12:51,025 basehttp 14521 127272374748864 "OPTIONS /api/career/jobs HTTP/1.1" 200 0
INFO 2025-11-24 14:12:51,037 basehttp 14521 127272383141568 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 14:12:51,042 basehttp 14521 127272374748864 "GET /api/career/jobs HTTP/1.1" 200 4675
INFO 2025-11-24 14:12:51,092 basehttp 14521 127272383141568 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 14:13:03,068 basehttp 14521 127272374748864 "GET /api/career/jobs HTTP/1.1" 200 4675
INFO 2025-11-24 14:13:03,080 basehttp 14521 127272383141568 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 14:13:03,090 basehttp 14521 127272374748864 "GET /api/career/jobs HTTP/1.1" 200 4675
INFO 2025-11-24 14:13:03,100 basehttp 14521 127272383141568 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 14:13:03,142 basehttp 14521 127272374748864 "GET /api/career/jobs HTTP/1.1" 200 4675
INFO 2025-11-24 14:13:03,158 basehttp 14521 127272399926976 "GET /api/career/jobs HTTP/1.1" 200 4675
INFO 2025-11-24 14:13:03,172 basehttp 14521 127272391534272 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 14:13:03,172 basehttp 14521 127272383141568 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 14:13:06,231 basehttp 14521 127272383141568 "OPTIONS /api/blog/categories/ HTTP/1.1" 200 0
INFO 2025-11-24 14:13:06,238 basehttp 14521 127272374748864 "OPTIONS /api/blog/posts/?page=1&page_size=6 HTTP/1.1" 200 0
INFO 2025-11-24 14:13:06,262 basehttp 14521 127272391534272 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 14:13:06,262 basehttp 14521 127272399926976 "GET /api/career/jobs HTTP/1.1" 200 4675
INFO 2025-11-24 14:13:06,299 basehttp 14521 127272383141568 "GET /api/blog/categories/ HTTP/1.1" 200 1418
INFO 2025-11-24 14:13:06,311 basehttp 14521 127272391534272 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 14:13:06,321 basehttp 14521 127272399926976 "GET /api/career/jobs HTTP/1.1" 200 4675
INFO 2025-11-24 14:13:06,326 basehttp 14521 127272374748864 "GET /api/blog/posts/?page=1&page_size=6 HTTP/1.1" 200 5121
INFO 2025-11-24 14:13:06,352 basehttp 14521 127272383141568 "GET /api/blog/categories/ HTTP/1.1" 200 1418
INFO 2025-11-24 14:13:06,363 basehttp 14521 127272391534272 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 14:13:06,399 basehttp 14521 127272374748864 "GET /api/blog/posts/?page=1&page_size=6 HTTP/1.1" 200 5121
INFO 2025-11-24 14:13:06,413 basehttp 14521 127272383141568 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 14:13:18,876 basehttp 14521 127272026633920 "GET /admin/ HTTP/1.1" 302 0
INFO 2025-11-24 14:13:18,898 basehttp 14521 127272026633920 "GET /admin/login/?next=/admin/ HTTP/1.1" 200 4173
INFO 2025-11-24 14:13:18,993 basehttp 14521 127272026633920 "GET /static/admin/css/base.css HTTP/1.1" 200 22120
INFO 2025-11-24 14:13:18,995 basehttp 14521 127272000407232 "GET /static/admin/css/nav_sidebar.css HTTP/1.1" 200 2810
INFO 2025-11-24 14:13:18,996 basehttp 14521 127272017192640 "GET /static/admin/css/dark_mode.css HTTP/1.1" 200 2808
INFO 2025-11-24 14:13:18,996 basehttp 14521 127272008799936 "GET /static/admin/js/theme.js HTTP/1.1" 200 1653
INFO 2025-11-24 14:13:18,998 basehttp 14521 127271990965952 "GET /static/admin/js/nav_sidebar.js HTTP/1.1" 200 3063
INFO 2025-11-24 14:13:18,999 basehttp 14521 127271982573248 "GET /static/admin/css/login.css HTTP/1.1" 200 951
INFO 2025-11-24 14:13:19,001 basehttp 14521 127272000407232 "GET /static/admin/css/responsive.css HTTP/1.1" 200 16565
WARNING 2025-11-24 14:13:19,077 log 14521 127272000407232 Not Found: /favicon.ico
WARNING 2025-11-24 14:13:19,077 basehttp 14521 127272000407232 "GET /favicon.ico HTTP/1.1" 404 3043
INFO 2025-11-24 14:13:21,374 basehttp 14521 127272026633920 "POST /admin/login/?next=/admin/ HTTP/1.1" 200 4339
INFO 2025-11-24 14:14:51,936 basehttp 14521 127272026633920 "POST /admin/login/?next=/admin/ HTTP/1.1" 302 0
INFO 2025-11-24 14:14:51,973 basehttp 14521 127272026633920 "GET /admin/ HTTP/1.1" 200 39055
INFO 2025-11-24 14:14:52,071 basehttp 14521 127272000407232 "GET /static/admin/css/dashboard.css HTTP/1.1" 200 441
INFO 2025-11-24 14:14:52,088 basehttp 14521 127272000407232 "GET /static/admin/img/icon-addlink.svg HTTP/1.1" 200 331
INFO 2025-11-24 14:14:52,088 basehttp 14521 127271990965952 "GET /static/admin/img/icon-changelink.svg HTTP/1.1" 200 380
INFO 2025-11-24 14:14:52,090 basehttp 14521 127271990965952 "GET /static/admin/img/icon-deletelink.svg HTTP/1.1" 200 392
INFO 2025-11-24 14:15:16,828 basehttp 14521 127272026633920 "GET /admin/case_studies/client/9/change/ HTTP/1.1" 302 0
INFO 2025-11-24 14:15:16,866 basehttp 14521 127272026633920 "GET /admin/ HTTP/1.1" 200 39224
INFO 2025-11-24 14:15:16,962 basehttp 14521 127272000407232 "GET /static/admin/img/icon-alert.svg HTTP/1.1" 200 504
INFO 2025-11-24 14:15:20,705 basehttp 14521 127272026633920 "GET /admin/about/aboutmilestone/ HTTP/1.1" 200 34312
INFO 2025-11-24 14:15:20,759 basehttp 14521 127272000407232 "GET /static/admin/css/changelists.css HTTP/1.1" 200 6878
INFO 2025-11-24 14:15:20,761 basehttp 14521 127272017192640 "GET /static/admin/js/jquery.init.js HTTP/1.1" 200 347
INFO 2025-11-24 14:15:20,761 basehttp 14521 127272008799936 "GET /static/admin/js/core.js HTTP/1.1" 200 6208
INFO 2025-11-24 14:15:20,763 basehttp 14521 127271982573248 "GET /static/admin/js/admin/RelatedObjectLookups.js HTTP/1.1" 200 9777
INFO 2025-11-24 14:15:20,769 basehttp 14521 127272000407232 "GET /static/admin/js/actions.js HTTP/1.1" 200 8076
INFO 2025-11-24 14:15:20,770 basehttp 14521 127272017192640 "GET /static/admin/js/prepopulate.js HTTP/1.1" 200 1531
INFO 2025-11-24 14:15:20,772 basehttp 14521 127272008799936 "GET /static/admin/js/urlify.js HTTP/1.1" 200 7887
INFO 2025-11-24 14:15:20,774 basehttp 14521 127271990965952 "GET /admin/jsi18n/ HTTP/1.1" 200 3342
INFO 2025-11-24 14:15:20,777 basehttp 14521 127272026633920 "GET /static/admin/js/vendor/jquery/jquery.js HTTP/1.1" 200 285314
INFO 2025-11-24 14:15:20,778 basehttp 14521 127271990965952 "GET /static/admin/js/filters.js HTTP/1.1" 200 978
INFO 2025-11-24 14:15:20,780 basehttp 14521 127271982573248 "GET /static/admin/js/vendor/xregexp/xregexp.js HTTP/1.1" 200 325171
INFO 2025-11-24 14:15:20,859 basehttp 14521 127271982573248 "GET /static/admin/img/tooltag-add.svg HTTP/1.1" 200 331
INFO 2025-11-24 14:15:20,860 basehttp 14521 127272000407232 "GET /static/admin/img/sorting-icons.svg HTTP/1.1" 200 1097
INFO 2025-11-24 14:15:23,371 basehttp 14521 127271982573248 "GET /admin/about/aboutmilestone/27/change/ HTTP/1.1" 200 34486
INFO 2025-11-24 14:15:23,411 basehttp 14521 127271982573248 "GET /static/admin/css/forms.css HTTP/1.1" 200 8525
INFO 2025-11-24 14:15:23,416 basehttp 14521 127272000407232 "GET /admin/jsi18n/ HTTP/1.1" 200 3342
INFO 2025-11-24 14:15:23,417 basehttp 14521 127272008799936 "GET /static/admin/js/change_form.js HTTP/1.1" 200 606
INFO 2025-11-24 14:15:23,418 basehttp 14521 127271990965952 "GET /static/admin/js/prepopulate_init.js HTTP/1.1" 200 586
INFO 2025-11-24 14:15:23,453 basehttp 14521 127272000407232 "GET /static/admin/img/icon-viewlink.svg HTTP/1.1" 200 581
INFO 2025-11-24 14:15:23,458 basehttp 14521 127271982573248 "GET /static/admin/css/widgets.css HTTP/1.1" 200 11973
INFO 2025-11-24 14:15:29,674 basehttp 14521 127271982573248 "GET /admin/about/aboutmilestone/ HTTP/1.1" 200 34312
INFO 2025-11-24 14:15:33,502 basehttp 14521 127271982573248 "GET /admin/about/aboutmilestone/25/change/ HTTP/1.1" 200 34446
INFO 2025-11-24 14:15:33,546 basehttp 14521 127271982573248 "GET /admin/jsi18n/ HTTP/1.1" 200 3342
INFO 2025-11-24 14:15:38,415 basehttp 14521 127271982573248 "GET /admin/about/aboutmilestone/ HTTP/1.1" 200 34312
INFO 2025-11-24 14:16:01,816 basehttp 14521 127272391534272 "OPTIONS /api/about/page/ HTTP/1.1" 200 0
INFO 2025-11-24 14:16:01,837 basehttp 14521 127272399926976 "GET /api/career/jobs HTTP/1.1" 200 4675
INFO 2025-11-24 14:16:01,840 basehttp 14521 127272383141568 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 14:16:01,867 basehttp 14521 127272399926976 "GET /api/career/jobs HTTP/1.1" 200 4675
INFO 2025-11-24 14:16:01,879 basehttp 14521 127272383141568 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 14:16:01,879 basehttp 14521 127272374748864 "GET /api/about/page/ HTTP/1.1" 200 8597
INFO 2025-11-24 14:16:01,917 basehttp 14521 127272391534272 "GET /api/about/page/ HTTP/1.1" 200 8597
INFO 2025-11-24 14:16:01,929 basehttp 14521 127272374748864 "GET /api/about/page/ HTTP/1.1" 200 8597
INFO 2025-11-24 14:16:01,939 basehttp 14521 127272383141568 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 14:16:01,957 basehttp 14521 127272391534272 "GET /api/about/page/ HTTP/1.1" 200 8597
INFO 2025-11-24 14:16:02,019 basehttp 14521 127272383141568 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 14:16:02,026 basehttp 14521 127272399926976 "GET /api/about/page/ HTTP/1.1" 200 8597
INFO 2025-11-24 14:16:02,034 basehttp 14521 127272374748864 "GET /api/about/page/ HTTP/1.1" 200 8597
INFO 2025-11-24 14:16:10,126 basehttp 14521 127272399926976 "OPTIONS /api/case-studies/ HTTP/1.1" 200 0
INFO 2025-11-24 14:16:10,147 basehttp 14521 127272383141568 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 14:16:10,156 basehttp 14521 127272391534272 "GET /api/career/jobs HTTP/1.1" 200 4675
INFO 2025-11-24 14:16:10,160 basehttp 14521 127272374748864 "GET /api/case-studies/ HTTP/1.1" 200 5617
INFO 2025-11-24 14:16:10,175 basehttp 14521 127272399926976 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 14:16:10,184 basehttp 14521 127272383141568 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 14:16:10,193 basehttp 14521 127272391534272 "GET /api/career/jobs HTTP/1.1" 200 4675
INFO 2025-11-24 14:16:10,193 basehttp 14521 127272374748864 "GET /api/case-studies/ HTTP/1.1" 200 5617
INFO 2025-11-24 14:16:10,232 basehttp 14521 127272399926976 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 14:16:15,085 basehttp 14521 127272374748864 "OPTIONS /api/case-studies/?ordering=display_order&page_size=5 HTTP/1.1" 200 0
INFO 2025-11-24 14:16:15,085 basehttp 14521 127272383141568 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 14:16:15,085 basehttp 14521 127272391534272 "OPTIONS /api/home/banner/ HTTP/1.1" 200 0
INFO 2025-11-24 14:16:15,085 basehttp 14521 127271972083392 "OPTIONS /api/blog/posts/latest/?limit=12 HTTP/1.1" 200 0
INFO 2025-11-24 14:16:15,102 basehttp 14521 127272391534272 "GET /api/home/banner/ HTTP/1.1" 200 3438
INFO 2025-11-24 14:16:15,108 basehttp 14521 127272399926976 "GET /api/career/jobs HTTP/1.1" 200 4675
INFO 2025-11-24 14:16:15,116 basehttp 14521 127271972083392 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 14:16:15,121 basehttp 14521 127272383141568 "GET /api/case-studies/?ordering=display_order&page_size=5 HTTP/1.1" 200 4744
INFO 2025-11-24 14:16:15,153 basehttp 14521 127272399926976 "GET /api/career/jobs HTTP/1.1" 200 4675
INFO 2025-11-24 14:16:15,156 basehttp 14521 127272374748864 "GET /api/blog/posts/latest/?limit=12 HTTP/1.1" 200 8374
INFO 2025-11-24 14:16:15,174 basehttp 14521 127272391534272 "GET /api/home/banner/ HTTP/1.1" 200 3438
INFO 2025-11-24 14:16:15,185 basehttp 14521 127271972083392 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 14:16:15,194 basehttp 14521 127272383141568 "GET /api/case-studies/?ordering=display_order&page_size=5 HTTP/1.1" 200 4744
INFO 2025-11-24 14:16:15,201 basehttp 14521 127271490811584 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 14:16:15,276 basehttp 14521 127271490811584 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 14:16:15,294 basehttp 14521 127271972083392 "GET /api/services/?ordering=display_order&page=1 HTTP/1.1" 200 10722
INFO 2025-11-24 14:16:15,306 basehttp 14521 127272399926976 "GET /api/blog/posts/latest/?limit=12 HTTP/1.1" 200 8374
INFO 2025-11-24 14:17:22,716 autoreload 14521 127272466882688 /home/gnx/Desktop/GNX-WEB/backEnd/gnx/settings.py changed, reloading.
INFO 2025-11-24 14:17:23,283 autoreload 40629 126035405783168 Watching for file changes with StatReloader
INFO 2025-11-24 14:17:24,688 autoreload 40629 126035405783168 /home/gnx/Desktop/GNX-WEB/backEnd/gnx/settings.py changed, reloading.
INFO 2025-11-24 14:17:25,211 autoreload 40650 134584020750464 Watching for file changes with StatReloader
INFO 2025-11-24 14:17:52,231 autoreload 40650 134584020750464 /home/gnx/Desktop/GNX-WEB/backEnd/gnx/settings.py changed, reloading.
INFO 2025-11-24 14:17:52,734 autoreload 41027 131304750628992 Watching for file changes with StatReloader
INFO 2025-11-24 14:17:55,154 autoreload 41027 131304750628992 /home/gnx/Desktop/GNX-WEB/backEnd/gnx/middleware/admin_ip_restriction.py changed, reloading.
INFO 2025-11-24 14:17:55,632 autoreload 41073 132612486312064 Watching for file changes with StatReloader
INFO 2025-11-24 14:26:08,072 autoreload 41073 132612486312064 /home/gnx/Desktop/GNX-WEB/backEnd/gnx/settings.py changed, reloading.
INFO 2025-11-24 14:26:08,639 autoreload 44508 130053635125376 Watching for file changes with StatReloader
INFO 2025-11-24 14:34:01,218 autoreload 47585 126321009672320 Watching for file changes with StatReloader

View File

@@ -2,9 +2,12 @@
# This configuration shows how to set up nginx as a reverse proxy
# to secure the Django API backend
# Generate a secure API key for INTERNAL_API_KEY:
# python -c "import secrets; print(secrets.token_urlsafe(32))"
# Add this key to your Django .env file as INTERNAL_API_KEY
# API Key Configuration:
# - In DEBUG mode, Django will auto-generate a secure API key if not set
# - To get the current API key, run: python manage.py show_api_key
# - Add the key to your Django .env file as INTERNAL_API_KEY
# - Use the same key in this nginx config (see line 69)
# - In production, you MUST set INTERNAL_API_KEY explicitly in .env
upstream django_backend {
# Django backend running on internal network only
@@ -66,6 +69,8 @@ server {
# Add custom header to prove request came through nginx
# This value must match INTERNAL_API_KEY in Django settings
# Get the current key with: python manage.py show_api_key
# In development, Django auto-generates this key if not set
set $api_key "YOUR_SECURE_API_KEY_HERE";
proxy_set_header X-Internal-API-Key $api_key;
@@ -123,6 +128,7 @@ server {
# deny all;
# Same proxy settings as /api/
# Use the same API key as /api/ location above
set $api_key "YOUR_SECURE_API_KEY_HERE";
proxy_set_header X-Internal-API-Key $api_key;
proxy_set_header Host $host;

View File

@@ -44,9 +44,15 @@ CORS_ALLOW_CREDENTIALS=True
CSRF_TRUSTED_ORIGINS=https://gnxsoft.com,https://www.gnxsoft.com
# API Security - Internal API Key (nginx will add this header)
# REQUIRED in production! Auto-generated only in DEBUG mode.
# Generate a secure key: python -c "import secrets; print(secrets.token_urlsafe(32))"
# Or get current key: python manage.py show_api_key
INTERNAL_API_KEY=your-secure-api-key-here-change-this-in-production
# Admin IP Restriction - Only these IPs can access Django admin
# Comma-separated list of IP addresses or CIDR networks (e.g., 193.194.155.249 or 192.168.1.0/24)
ADMIN_ALLOWED_IPS=193.194.155.249
# Static Files
STATIC_ROOT=/var/www/gnx/staticfiles/
MEDIA_ROOT=/var/www/gnx/media/

17
backEnd/requirements.txt Normal file
View File

@@ -0,0 +1,17 @@
asgiref==3.11.0
Django==5.2.8
django-cors-headers==4.9.0
django-filter==25.2
djangorestframework==3.16.1
drf-yasg==1.21.11
gunicorn==21.2.0
inflection==0.5.1
packaging==25.0
pillow==12.0.0
psycopg2-binary==2.9.9
dj-database-url==2.1.0
python-decouple==3.8
pytz==2025.2
PyYAML==6.0.3
sqlparse==0.5.3
uritemplate==4.2.0