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
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
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:
| Decorator | File | Effect |
|---|---|---|
@jwt_required() | Flask-JWT-Extended | Validates JWT and makes current_user available via @jwt.user_lookup_loader |
@admin_required() | app/auth/auth.py | Checks claims['role'] == 'admin'; returns 1403 if not |
@permission_required(Permission.X) | app/auth/auth.py | Checks claims['permissions'] for the required value(s); returns 401 if missing |
@jwt_or_redirect(url) | app/auth/auth.py | For browser routes — reads JWT from cookie, redirects to url if not authenticated |
Token validation detail
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:
| Code | Meaning |
|---|---|
401 | Missing token, invalid format, or permission denied |
440 | Token found but revoked or expired in token_logs_container |
1403 | Valid JWT but caller is not an admin (admin_required check failed) |
📚 Next Steps
- Adding Features Guide - Detailed examples
- Contributing Guide - Branch workflow and PR checklist