Skip to main content

Project Structure

This guide explains the codebase organization and how to add new features to AquaGen API.

📁 Directory Structure

aquagenapi/
├── app/ # Main application
│ ├── __init__.py # Flask app initialization
│ ├── config.py # Configuration with Azure Key Vault
│ ├── app.py # Application entry point
│ │
│ ├── apis/ # API Blueprints
│ │ ├── user.py # User API (31 namespaces)
│ │ ├── admin.py # Admin API (13 namespaces)
│ │ ├── external.py # External integrations
│ │ └── qa.py # QA endpoints
│ │
│ ├── routes/ # Route handlers (33 files)
│ │ ├── report.py # Report generation
│ │ ├── device_data.py # Device data endpoints
│ │ ├── device_data_v2.py # Enhanced device data
│ │ ├── alerts_routes.py # Alert management
│ │ ├── notification.py # Notifications
│ │ ├── landing_page.py # Dashboard data
│ │ ├── water_balance.py # Water balance
│ │ ├── granular_data.py # Granular sensor data
│ │ ├── user.py # User operations
│ │ ├── admin/ # Admin routes (15 files)
│ │ │ ├── admin_login.py
│ │ │ ├── automated_report.py
│ │ │ ├── industry.py
│ │ │ ├── unit.py
│ │ │ ├── user.py
│ │ │ └── ...
│ │ └── external/ # External routes
│ │ ├── external_login.py
│ │ └── water_ratio_data.py
│ │
│ ├── services/ # Business logic (40+ services)
│ │ ├── report/ # Report services
│ │ │ ├── report.py # Main dispatcher
│ │ │ └── report_types/ # 18 report implementations
│ │ │ ├── flow_report.py
│ │ │ ├── energy_report.py
│ │ │ ├── level_report.py
│ │ │ ├── quality_report.py
│ │ │ ├── borewell_report.py
│ │ │ ├── alert_report.py
│ │ │ ├── consolidated_report.py
│ │ │ ├── water_balance_report.py
│ │ │ └── ...
│ │ ├── device_data.py
│ │ ├── device_data_v2/ # Enhanced device data
│ │ │ ├── device_data_v2.py
│ │ │ └── fetch_data/
│ │ ├── alerts/ # Alert system
│ │ │ ├── alerts_processor.py
│ │ │ ├── alerts_content/ # Alert templates
│ │ │ ├── channels/ # Email, SMS, Chat
│ │ │ └── constants/
│ │ ├── admin/ # Admin services (15 files)
│ │ │ ├── admin_login.py
│ │ │ ├── industry.py
│ │ │ ├── unit.py
│ │ │ ├── user.py
│ │ │ └── automated_report/
│ │ │ ├── automated_report.py
│ │ │ └── report_processor.py
│ │ ├── notification.py
│ │ ├── summary/ # Summary services
│ │ ├── landing_page/ # Dashboard logic
│ │ ├── water_balance.py
│ │ ├── granula_data_service.py
│ │ ├── water_neutrality_service.py
│ │ ├── powerdrill/ # PowerDrill integration
│ │ ├── quality_data_extraction/
│ │ ├── graph_manager/
│ │ ├── info_builders/
│ │ ├── wri/ # WRI bathymetry
│ │ ├── firebase/
│ │ ├── sms/
│ │ ├── secret_vault_service.py
│ │ └── web_socket.py
│ │
│ ├── formatters/ # Data formatting (52 files)
│ │ ├── report/ # Report formatters
│ │ │ ├── report.py
│ │ │ ├── report_util.py
│ │ │ ├── xlsx_formatter/
│ │ │ ├── flow_formatter.py
│ │ │ ├── energy_formatter.py
│ │ │ ├── level_formatter.py
│ │ │ ├── quality_formatter.py
│ │ │ └── ...
│ │ ├── device_data_formatter.py
│ │ ├── device_data_v2/ # Polymorphic formatters
│ │ │ ├── device_data_v2_formatter.py
│ │ │ └── formatters/
│ │ │ ├── flow_device_data_formatter.py
│ │ │ ├── energy_device_data_formatter.py
│ │ │ ├── quality_device_data_formatter.py
│ │ │ └── ...
│ │ ├── alerts_formatter.py
│ │ ├── common_data_formatter.py
│ │ ├── graph_data_formatter.py
│ │ └── ...
│ │
│ ├── database/ # Database layer
│ │ ├── database_config.py # Cosmos DB setup
│ │ ├── database_supporter.py # 200+ query methods
│ │ └── queires_list.py # Query templates
│ │
│ ├── models/ # Data models (30 files)
│ │ ├── error_models.py
│ │ ├── success_models.py
│ │ ├── user_models.py
│ │ ├── industry_models.py
│ │ ├── unit_models.py
│ │ ├── device_data_models.py
│ │ ├── alerts_models.py
│ │ ├── report_models.py
│ │ └── ...
│ │
│ ├── data_class/ # Data classes & DTOs
│ │ ├── report.py
│ │ ├── device_data_v2_args.py
│ │ ├── water_ratio.py
│ │ └── alert/
│ │
│ ├── enum/ # Enums (20 files)
│ │ ├── service_type.py
│ │ ├── report_type.py
│ │ ├── report_format.py
│ │ ├── category_type.py
│ │ ├── response_status.py
│ │ └── ...
│ │
│ ├── templates/ # Jinja2 templates (55 files)
│ │ ├── common/
│ │ ├── flow/ # Water reports (7 templates)
│ │ ├── energy/ # Energy reports (6 templates)
│ │ ├── level/ # Level reports (3 templates)
│ │ ├── quality/ # Quality reports (4 templates)
│ │ ├── borewell/ # Borewell reports (4 templates)
│ │ ├── consolidated/
│ │ ├── summary/
│ │ ├── alert/
│ │ └── ...
│ │
│ ├── validators/ # Input validation
│ │ ├── schema.py
│ │ ├── validator_util.py
│ │ └── schemas/ # JSON schemas
│ │
│ ├── security_checks/ # Security layer
│ │ ├── input_validator.py # SQL injection prevention
│ │ └── track_user.py # IP tracking
│ │
│ ├── util/ # Utilities (10 files)
│ │ ├── date_time_util.py
│ │ ├── number_util.py
│ │ ├── calculation_util.py
│ │ ├── si_unit_util.py
│ │ ├── sort_util.py
│ │ └── ...
│ │
│ ├── auth/ # Authentication
│ │ └── auth.py
│ │
│ ├── constants/ # App constants
│ │ ├── constants.py
│ │ └── templates.py
│ │
│ ├── cachedData/ # In-memory cache
│ │ └── cachedData.py
│ │
│ ├── data/ # Static data files
│ │ ├── bathyData.py
│ │ └── radhakunj_boundary.geojson
│ │
│ ├── urls/ # URL configuration
│ │
│ └── logging_config.py

├── scripts/ # Automation scripts
│ └── auto_reports.sh

├── docs/ # Documentation
│ ├── intro.md
│ ├── getting-started.md
│ ├── architecture/
│ ├── core-concepts/
│ ├── api-reference/
│ ├── guides/
│ ├── deployment/
│ ├── advanced/
│ └── development/

├── data/ # Data files

├── requirements.txt # Dependencies
├── app.py # Entry point
├── CLAUDE.md # Development guidelines
└── README.md

🔓 Public Route Handling (app/auth/auth.py)

Every request passes through auth_interceptor(), registered via @app.before_request. It decides whether to enforce JWT auth or let the request through unauthenticated.

Decision flow

Auth interceptor pipeline
Request arrives

├── path in Constants.PUBLIC_ROUTES? → skip auth, continue
├── path starts with any PUBLIC_PREFIXES? → skip auth, continue
├── method == OPTIONS? → skip auth, continue (CORS preflight)

└── all other paths → validate_token_logic()

├── Extract token from Authorization: Bearer <token>
│ or fallback to ?jwt= query param

├── No token found? → 401 "Missing or invalid authorization token"

├── decode_token() fails? → 401 "Invalid token format"

├── industry_id != 'INTERNAL'?
│ └── TokenService.validate_token()
│ └── not valid? → 440 (session expired)

├── verify_jwt_in_request() → sets g.user_permissions, g.user_id, g.industry_id

└── exception during verify? → fallback: populate g.* from decoded token directly

Public routes (skip auth entirely)

Defined in Constants.PUBLIC_ROUTES — exact path matches:

/api/user/user/login
/api/user/user/logout
/api/user/user/otplogin
/api/user/user/refresh
/api/user/user/swaggerLogin
/api/user/user/validateCredentials
/api/user/batchProcess/
/api/admin/validate/unitReset
/api/admin/auth/login
/api/admin/auth/refresh
/api/admin/auth/swaggerLogin
/api/external/auth/swaggerLogin
/api/qa/auth/swaggerLogin
/api/qa/user/login

Defined in Constants.PUBLIC_PREFIXES — prefix matches (any path starting with these):

/api/user/docs              /api/admin/docs
/api/user/swagger.json /api/admin/swagger.json
/api/external/docs /api/external/swagger.json
/api/qa/docs /api/qa/swagger.json
/docs /swagger /swaggerui /static
/api/user/virtual
/api/admin/shift-monitoring
/api/admin/bpTrigger
/api/admin/bathyDataProcessor/process
/api/admin/bathyDataProcessor/recomputeStatic

Adding a new public route

tip

To expose an endpoint without JWT, add its path to the appropriate list in app/constants/constants.py:

# Exact path
PUBLIC_ROUTES = [
...
'/api/user/myFeature/public',
]

# Or prefix (all sub-paths will be exempt)
PUBLIC_PREFIXES = [
...
'/api/user/myFeature/webhook',
]

Route-level decorators (applied inside the handler)

Even after auth_interceptor passes, individual routes can apply additional checks:

DecoratorFileEffect
@jwt_required()Flask-JWT-ExtendedValidates JWT and makes current_user available via @jwt.user_lookup_loader
@admin_required()app/auth/auth.pyChecks claims['role'] == 'admin'; returns 1403 if not
@permission_required(Permission.X)app/auth/auth.pyChecks claims['permissions'] for the required value(s); returns 401 if missing
@jwt_or_redirect(url)app/auth/auth.pyFor browser routes — reads JWT from cookie, redirects to url if not authenticated

Token validation detail

note

TokenService.validate_token() checks that the token is still active in token_logs_container. This check is skipped for INTERNAL industry tokens (admin tokens) — admin sessions are validated by signature only.

Status codes returned by the auth layer:

CodeMeaning
401Missing token, invalid format, or permission denied
440Token found but revoked or expired in token_logs_container
1403Valid JWT but caller is not an admin (admin_required check failed)

📚 Next Steps