From f469cf78062e7dd58236f2b08d1a0cc271195ecf Mon Sep 17 00:00:00 2001 From: Iliyan Angelov Date: Fri, 21 Nov 2025 15:15:48 +0200 Subject: [PATCH] updates --- Backend/.env.example | 108 ++++++++++++++---- Backend/reset_user_passwords.py | 64 ----------- .../{ => seeds_data}/add_accountant_role.py | 0 Backend/{ => seeds_data}/seed_about_page.py | 0 .../{ => seeds_data}/seed_banners_company.py | 0 .../{ => seeds_data}/seed_homepage_footer.py | 0 Backend/{ => seeds_data}/seed_initial_data.py | 0 .../{ => seeds_data}/seed_luxury_content.py | 0 Backend/{ => seeds_data}/seed_policy_pages.py | 0 Backend/{ => seeds_data}/seed_rooms.py | 0 Backend/{ => seeds_data}/seed_users.py | 0 Backend/src/__pycache__/main.cpython-312.pyc | Bin 16348 -> 18818 bytes Backend/src/main.py | 45 ++++++++ 13 files changed, 129 insertions(+), 88 deletions(-) delete mode 100644 Backend/reset_user_passwords.py rename Backend/{ => seeds_data}/add_accountant_role.py (100%) rename Backend/{ => seeds_data}/seed_about_page.py (100%) rename Backend/{ => seeds_data}/seed_banners_company.py (100%) rename Backend/{ => seeds_data}/seed_homepage_footer.py (100%) rename Backend/{ => seeds_data}/seed_initial_data.py (100%) rename Backend/{ => seeds_data}/seed_luxury_content.py (100%) rename Backend/{ => seeds_data}/seed_policy_pages.py (100%) rename Backend/{ => seeds_data}/seed_rooms.py (100%) rename Backend/{ => seeds_data}/seed_users.py (100%) diff --git a/Backend/.env.example b/Backend/.env.example index 086a03ef..5aa5b0cd 100644 --- a/Backend/.env.example +++ b/Backend/.env.example @@ -1,34 +1,94 @@ +# ============================================ # Hotel Booking API - Environment Variables +# ============================================ # Copy this file to .env and fill in your actual values +# All variables are optional and have sensible defaults +# See src/config/settings.py for default values # ============================================ -# Email/SMTP Configuration +# Application Configuration # ============================================ -# SMTP Server Settings -SMTP_HOST=smtp.gmail.com -SMTP_PORT=587 -SMTP_USER=your-email@gmail.com -SMTP_PASSWORD=your-app-specific-password - -# Email Sender Information -SMTP_FROM_EMAIL=noreply@yourdomain.com -SMTP_FROM_NAME=Hotel Booking - -# Alternative: Legacy environment variable names (for backward compatibility) -# MAIL_HOST=smtp.gmail.com -# MAIL_PORT=587 -# MAIL_USER=your-email@gmail.com -# MAIL_PASS=your-app-specific-password -# MAIL_FROM=noreply@yourdomain.com -# MAIL_SECURE=false +ENVIRONMENT=development +# Options: development, staging, production +DEBUG=true +# Set to false in production +HOST=0.0.0.0 +PORT=8000 +API_V1_PREFIX=/api/v1 # ============================================ -# Other Required Variables +# Database Configuration # ============================================ -CLIENT_URL=http://localhost:5173 -DB_USER=root -DB_PASS=your_database_password -DB_NAME=hotel_db DB_HOST=localhost DB_PORT=3306 -JWT_SECRET=your-super-secret-jwt-key-change-in-production +DB_USER=root +DB_PASS= +# Leave empty if using MySQL without password +DB_NAME=hotel_db + +# ============================================ +# JWT Authentication +# ============================================ +# JWT_SECRET will be auto-generated on startup if not set +# The generated secret will be saved here automatically +JWT_ALGORITHM=HS256 +JWT_ACCESS_TOKEN_EXPIRE_MINUTES=30 +JWT_REFRESH_TOKEN_EXPIRE_DAYS=7 + +# ============================================ +# CORS & Client Configuration +# ============================================ +CLIENT_URL=http://localhost:5173 +# Frontend application URL +CORS_ORIGINS=["http://localhost:5173","http://localhost:3000","http://127.0.0.1:5173"] +# JSON array of allowed origins (only used in production) + +# ============================================ +# Email & Payment Settings +# ============================================ +# NOTE: Email (SMTP) and Payment Gateway (Stripe, PayPal) settings +# are configured in the Admin Dashboard, not via environment variables. +# Log in as admin and go to Settings page to configure these. + +# ============================================ +# Redis Configuration (Optional) +# ============================================ +REDIS_ENABLED=false +REDIS_HOST=localhost +REDIS_PORT=6379 +REDIS_DB=0 +REDIS_PASSWORD= +# Leave empty if Redis has no password + +# ============================================ +# File Upload Configuration +# ============================================ +UPLOAD_DIR=uploads +MAX_UPLOAD_SIZE=5242880 +# Max upload size in bytes (5MB default) +ALLOWED_EXTENSIONS=["jpg","jpeg","png","gif","webp"] +# JSON array + +# ============================================ +# Rate Limiting +# ============================================ +RATE_LIMIT_ENABLED=true +RATE_LIMIT_PER_MINUTE=60 + +# ============================================ +# Logging Configuration +# ============================================ +LOG_LEVEL=INFO +# Options: DEBUG, INFO, WARNING, ERROR, CRITICAL +LOG_FILE=logs/app.log +LOG_MAX_BYTES=10485760 +# Max log file size in bytes (10MB) +LOG_BACKUP_COUNT=5 + +# ============================================ +# Server Configuration +# ============================================ +REQUEST_TIMEOUT=30 +# Request timeout in seconds (0 to disable) +HEALTH_CHECK_INTERVAL=30 +# Health check interval in seconds diff --git a/Backend/reset_user_passwords.py b/Backend/reset_user_passwords.py deleted file mode 100644 index aeabe583..00000000 --- a/Backend/reset_user_passwords.py +++ /dev/null @@ -1,64 +0,0 @@ -#!/usr/bin/env python3 - -import sys -import os -import bcrypt - -sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'src')) - -from sqlalchemy.orm import Session -from src.config.database import SessionLocal -from src.models.user import User -from src.config.logging_config import setup_logging - -logger = setup_logging() - - -def hash_password(password: str) -> str: - password_bytes = password.encode('utf-8') - salt = bcrypt.gensalt() - hashed = bcrypt.hashpw(password_bytes, salt) - return hashed.decode('utf-8') - - -def reset_password(db: Session, email: str, new_password: str) -> bool: - user = db.query(User).filter(User.email == email).first() - - if not user: - print(f"❌ User with email '{email}' not found") - return False - - hashed_password = hash_password(new_password) - - user.password = hashed_password - db.commit() - db.refresh(user) - - print(f"✅ Password reset for {email}") - print(f" New password: {new_password}") - print(f" Hash length: {len(user.password)} characters") - print() - - return True - - -def main(): - db = SessionLocal() - - try: - print("="*80) - print("RESETTING TEST USER PASSWORDS") - print("="*80) - print() - - - except Exception as e: - logger.error(f"Error: {e}", exc_info=True) - print(f"\n❌ Error: {e}") - db.rollback() - finally: - db.close() - - -if __name__ == "__main__": - main() diff --git a/Backend/add_accountant_role.py b/Backend/seeds_data/add_accountant_role.py similarity index 100% rename from Backend/add_accountant_role.py rename to Backend/seeds_data/add_accountant_role.py diff --git a/Backend/seed_about_page.py b/Backend/seeds_data/seed_about_page.py similarity index 100% rename from Backend/seed_about_page.py rename to Backend/seeds_data/seed_about_page.py diff --git a/Backend/seed_banners_company.py b/Backend/seeds_data/seed_banners_company.py similarity index 100% rename from Backend/seed_banners_company.py rename to Backend/seeds_data/seed_banners_company.py diff --git a/Backend/seed_homepage_footer.py b/Backend/seeds_data/seed_homepage_footer.py similarity index 100% rename from Backend/seed_homepage_footer.py rename to Backend/seeds_data/seed_homepage_footer.py diff --git a/Backend/seed_initial_data.py b/Backend/seeds_data/seed_initial_data.py similarity index 100% rename from Backend/seed_initial_data.py rename to Backend/seeds_data/seed_initial_data.py diff --git a/Backend/seed_luxury_content.py b/Backend/seeds_data/seed_luxury_content.py similarity index 100% rename from Backend/seed_luxury_content.py rename to Backend/seeds_data/seed_luxury_content.py diff --git a/Backend/seed_policy_pages.py b/Backend/seeds_data/seed_policy_pages.py similarity index 100% rename from Backend/seed_policy_pages.py rename to Backend/seeds_data/seed_policy_pages.py diff --git a/Backend/seed_rooms.py b/Backend/seeds_data/seed_rooms.py similarity index 100% rename from Backend/seed_rooms.py rename to Backend/seeds_data/seed_rooms.py diff --git a/Backend/seed_users.py b/Backend/seeds_data/seed_users.py similarity index 100% rename from Backend/seed_users.py rename to Backend/seeds_data/seed_users.py diff --git a/Backend/src/__pycache__/main.cpython-312.pyc b/Backend/src/__pycache__/main.cpython-312.pyc index 437e175ddf52740c2f237b7a048d8d5f2b3d22ca..bdb57e06676a100e0181a6563edf729cb3bbf604 100644 GIT binary patch delta 6685 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 F_GGxSsC9yJ!#xtqe98AsWVE z3zrzXX#;Msu*Ybmd+;6$dyOXAjGK`Ju`e#2(;UUNr^#{SEK0EB;98-NEO*t%mg`9Q zTq$=%QuIQuxtTs=FKxjsRyt+IKH7>~E$la5p!@NDh@X0!Hd&F_T{$b>qE9WKOrKe< zBjwuWx$;>#VS&)CEB4$kJmX@b|1v48xzadD+i|fW@<)562qr|#U7|0$p35WPCr#w2f9cXWHBf5LBe36MA&1|S`f}95 z4kY4DI^3Srce~nLDZB01ra%n#$l+HK<3bgjZ!>RJjW2cZFqCn(#p%teJ%ssU^gBE` zAyetV^E<)d=zJcugk$8G{yqCM%S`1ib7p&#Eb42n9HP*cfwrx$50q`Ftu_KiDu3*-{N z2}iNO&gIm4P6E2m^AwV_>p!spr@uC>Rd6|ieSW|mtuAFdaviRNv=8^ue%wzZIKo1? z?e$P|^b)>ANAL)Z<2W6~qjU_9K~qRQM_(d?4XLgDw$_NPRkyXCN?2h>@3S&z5wi9n zO%~KPQZy-2=NkJ+1GbUgvbDx+t%FHfJkGPv;%(bVXKbzKY^}q#*7LU33%1sdgw;B) zdoKI+jU;iW^Ph1o&mHURJ;!WwxR|h}sTR1&yhen4RxQ3XK5)MUm!k|maO*i!$P7ME z3xdm01|O&p&J>dS@VZ%V!%4Gds3sAQBOW)657ZcE3dwz}T%ht+kPFnKh{q9+nqa{`EkKlFaGA8f@$ULKjda%cO2zbc<{;b54@L77COlqCx zr=&ISDOfhX3hIezq!)-i;7B_}d|)Z$V<9d5Ix*%5b}z*<(PXpHt%WKC^ROE=CGpg} zm_n}hA@fPnCuxVjK-#NwM`m*pv5_?F8g^}#$boVx=EH69>idw-Dx_9H+CGs!llitH zsT(#Bw;gSp&b$6@0-Z%?bER`iw{#Y5>6-tvXE0!a!L6i&aXaI^kk`>O2NamZ;=jYNA-vmb-4 zPV@cwG5ifu77cq@FmzNYY4I`^x{0o_aCn=;e2jk1BH^cKYCf{tjo3uPFuL|_qbe6H z=JfRRMhw!^vzRq-B1WP^%-wVZz0XcIy|-pD=XA7(oB?IboFg{Fl;&RNLxAQ3eDHTR z*jzXNP4kp<<0GU_dM|sgMt()bf5|Fp{9BRa4UbLqcojSG(nI-@!kP|jFD~8NI3!C? SWXDjY@+7-($ge!{oA_S{2;<8D diff --git a/Backend/src/main.py b/Backend/src/main.py index 18a043f5..6ca18dbb 100644 --- a/Backend/src/main.py +++ b/Backend/src/main.py @@ -11,6 +11,9 @@ from slowapi.errors import RateLimitExceeded from pathlib import Path from datetime import datetime import sys +import secrets +import os +import re from .config.settings import settings from .config.logging_config import setup_logging, get_logger from .config.database import engine, Base, get_db @@ -151,8 +154,50 @@ app.include_router(page_content_routes.router, prefix='/api') app.include_router(page_content_routes.router, prefix=settings.API_V1_PREFIX) logger.info('All routes registered successfully') +def ensure_jwt_secret(): + """Generate and save JWT secret if it's using the default value.""" + default_secret = 'dev-secret-key-change-in-production-12345' + current_secret = settings.JWT_SECRET + + if not current_secret or current_secret == default_secret: + new_secret = secrets.token_urlsafe(64) + + os.environ['JWT_SECRET'] = new_secret + + env_file = Path(__file__).parent.parent / '.env' + if env_file.exists(): + try: + env_content = env_file.read_text(encoding='utf-8') + + jwt_pattern = re.compile(r'^JWT_SECRET=.*$', re.MULTILINE) + + if jwt_pattern.search(env_content): + env_content = jwt_pattern.sub(f'JWT_SECRET={new_secret}', env_content) + else: + jwt_section_pattern = re.compile(r'(# =+.*JWT.*=+.*\n)', re.IGNORECASE | re.MULTILINE) + match = jwt_section_pattern.search(env_content) + if match: + insert_pos = match.end() + env_content = env_content[:insert_pos] + f'JWT_SECRET={new_secret}\n' + env_content[insert_pos:] + else: + env_content += f'\nJWT_SECRET={new_secret}\n' + + env_file.write_text(env_content, encoding='utf-8') + logger.info('✓ JWT secret generated and saved to .env file') + except Exception as e: + logger.warning(f'Could not update .env file: {e}') + logger.info(f'Generated JWT secret (add to .env manually): JWT_SECRET={new_secret}') + else: + logger.info(f'Generated JWT secret (add to .env file): JWT_SECRET={new_secret}') + + logger.info('✓ Secure JWT secret generated automatically') + else: + logger.info('✓ JWT secret is configured') + @app.on_event('startup') async def startup_event(): + ensure_jwt_secret() + logger.info(f'{settings.APP_NAME} started successfully') logger.info(f'Environment: {settings.ENVIRONMENT}') logger.info(f'Debug mode: {settings.DEBUG}')