fix telegram

This commit is contained in:
Claude Agent
2026-02-23 15:12:33 +00:00
parent 6c78fec8a7
commit 8bc567a9c5
426 changed files with 112478 additions and 1 deletions

View File

@@ -69,7 +69,8 @@ def format_dashboard_response(data: Dict[str, Any], company_name: str = None) ->
if grupe_crt: if grupe_crt:
items = [f"{g.get('label', '')} {round(float(g.get('curent', 0))):,}" for g in grupe_crt] items = [f"{g.get('label', '')} {round(float(g.get('curent', 0))):,}" for g in grupe_crt]
total_crt = sum(round(float(g.get('curent', 0))) for g in grupe_crt) total_crt = sum(round(float(g.get('curent', 0))) for g in grupe_crt)
text += f"\n _Curent: {' \u00b7 '.join(items)} = {total_crt:,}_\n" sep = ' · '
text += f"\n _Curent: {sep.join(items)} = {total_crt:,}_\n"
return text return text

View File

@@ -0,0 +1,23 @@
================================================================================
ROA2WEB DEPLOYMENT PACKAGE
Generated: 2026-02-23 15:12:31
From: Linux/LXC deployment script
================================================================================
CONTENTS:
---------
backend/ Unified FastAPI backend
frontend/ Vue.js SPA (production build)
shared/ Shared Python modules
config/ Configuration templates
scripts/ PowerShell deployment scripts
DEPLOYMENT:
-----------
Server will auto-deploy within 5 minutes (Check-And-Deploy.ps1 scheduled task)
Or manually:
cd scripts
.\ROA2WEB-Console.ps1 -NonInteractive -Action DeployAll
================================================================================

View File

@@ -0,0 +1,179 @@
# ============================================================================
# ROA2WEB Unified Backend - Environment Configuration Template
# ============================================================================
# Single backend process serving Reports, Data Entry, and Telegram modules
#
# SETUP INSTRUCTIONS:
# 1. Copy this template: cp .env.example .env.dev
# 2. Fill in your actual values in .env.dev
# 3. Run: ./start-dev.sh (auto-copies .env.dev to .env)
#
# ENVIRONMENT FILES:
# - .env.dev → Development config (committed to git with real values)
# - .env.test → Test config (committed to git)
# - .env.prod → Production config template (committed, use placeholders!)
# - .env → Active config (auto-generated, NOT committed)
#
# IMPORTANT: Never manually edit .env - edit .env.dev instead!
# ============================================================================
# ORACLE DATABASE CONFIGURATION
# ============================================================================
# Single server: Use ORACLE_USER/HOST/PORT/SID
# Multi-server: Use ORACLE_SERVERS JSON (ignores single server vars)
# Passwords: secrets/{id}.oracle_pass
# SSH tunnels: ssh-tunnels.json (separate file)
ORACLE_USER=CONTAFIN_ORACLE
ORACLE_PASSWORD=SET_IN_SECRETS_FILE
ORACLE_HOST=localhost
ORACLE_PORT=1521
ORACLE_SID=ROA
# Multi-server example (uncomment to use):
# ORACLE_SERVERS='[{"id":"server1","name":"Server 1","host":"localhost","port":1521,"user":"USER","sid":"ROA"}]'
# ============================================================================
# JWT AUTHENTICATION (REQUIRED - Shared by all modules)
# ============================================================================
# Used for JWT token generation and validation (shared/auth/jwt_handler.py)
# Generate strong secret: python3 -c "import secrets; print(secrets.token_urlsafe(32))"
JWT_SECRET_KEY=GENERATE_STRONG_SECRET_IN_PRODUCTION
JWT_ALGORITHM=HS256
# Token expiration settings (used by shared/auth/jwt_handler.py)
ACCESS_TOKEN_EXPIRE_MINUTES=30
REFRESH_TOKEN_EXPIRE_DAYS=7
# ============================================================================
# SESSION SECURITY - EMAIL 2FA (REQUIRED for Telegram email login)
# ============================================================================
# Used by Telegram module for session token validation
# Generate with: python3 -c "import secrets; print(secrets.token_urlsafe(32))"
AUTH_SESSION_SECRET=your-secure-random-secret-here-min-32-chars
# ============================================================================
# SERVER CONFIGURATION
# ============================================================================
# Unified backend server settings
API_HOST=0.0.0.0
API_PORT=8000
DEBUG=false
# CORS Origins (comma-separated)
CORS_ORIGINS=http://localhost:3000,http://localhost:5173
# ============================================================================
# REPORTS MODULE - CACHE CONFIGURATION (OPTIONAL - defaults provided)
# ============================================================================
# Two-tier hybrid cache system (L1: in-memory LRU, L2: SQLite persistent)
# Used by backend/modules/reports/cache/config.py
# Core Settings
REPORTS_CACHE_ENABLED=True
REPORTS_CACHE_TYPE=hybrid
REPORTS_CACHE_SQLITE_PATH=./data/cache/roa2web_cache.db
REPORTS_CACHE_MEMORY_MAX_SIZE=1000
REPORTS_CACHE_DEFAULT_TTL=900
# TTL per Cache Type (seconds)
REPORTS_CACHE_TTL_SCHEMA=86400
REPORTS_CACHE_TTL_COMPANIES=1800
REPORTS_CACHE_TTL_DASHBOARD_SUMMARY=1800
REPORTS_CACHE_TTL_DASHBOARD_TRENDS=1800
REPORTS_CACHE_TTL_INVOICES=600
REPORTS_CACHE_TTL_INVOICES_SUMMARY=900
REPORTS_CACHE_TTL_TREASURY=600
# Maintenance
REPORTS_CACHE_CLEANUP_INTERVAL=3600
# Event-Based Invalidation (experimental)
REPORTS_CACHE_AUTO_INVALIDATE=False
REPORTS_CACHE_CHECK_INTERVAL=300
# Performance Tracking
REPORTS_CACHE_TRACK_PERFORMANCE=True
REPORTS_CACHE_BENCHMARK_ON_STARTUP=False
# ============================================================================
# DATA ENTRY MODULE - CONFIGURATION
# ============================================================================
# Data Entry module settings (receipts, OCR, etc.)
# SQLite Database
DATA_ENTRY_SQLITE_DATABASE_PATH=data/receipts/receipts.db
# File uploads
DATA_ENTRY_UPLOAD_PATH=data/receipts/uploads
DATA_ENTRY_MAX_UPLOAD_SIZE_MB=10
# ============================================================================
# OCR ENGINE CONFIGURATION
# ============================================================================
# Control which OCR engines are loaded at startup.
# Disabling engines saves memory but limits available OCR modes.
# Enable/disable PaddleOCR (set to 'false' to save ~800MB RAM)
# When disabled: 'paddleocr' engine unavailable
OCR_ENABLE_PADDLEOCR=false
# Enable/disable Tesseract (set to 'false' to save ~50MB RAM)
# When disabled: 'tesseract' engine unavailable
OCR_ENABLE_TESSERACT=false
# Default OCR engine when not specified in request
# Options: tesseract, doctr, doctr_plus, paddleocr
# Recommended: doctr_plus (2-tier sequential with early exit, ~7.5s avg)
OCR_DEFAULT_ENGINE=doctr_plus
# Active OCR engines shown in frontend dropdown (comma-separated)
# Options: tesseract, doctr, doctr_plus, paddleocr
# doctr_plus: 73.3% perfect, 7.5s avg, 65% fast path (recommended)
# doctr: 63.3% perfect, simpler but faster
OCR_ACTIVE_ENGINES=tesseract,doctr,doctr_plus,paddleocr
# OCR Worker Pool Configuration
# Number of parallel OCR workers (each loads ~1GB for docTR)
# Recommended: 2 for 8GB RAM, 3 for 16GB RAM
OCR_WORKERS=2
# Max tasks per worker before restart (0 = no restart, saves 40-60s warmup time)
# Set to 0 for testing, 10-20 for production (prevents memory leaks)
OCR_MAX_TASKS_PER_CHILD=0
# ============================================================================
# TELEGRAM MODULE - BOT CONFIGURATION (REQUIRED for Telegram features)
# ============================================================================
# Obtain bot token from @BotFather on Telegram
TELEGRAM_BOT_TOKEN=your_bot_token_here
# ============================================================================
# TELEGRAM MODULE - EMAIL AUTHENTICATION (SMTP) (REQUIRED for email 2FA)
# ============================================================================
# Required for email-based 2FA authentication flow
# Users can login with email + password instead of web app linking
# SMTP Server Configuration
TELEGRAM_SMTP_HOST=mail.romfast.ro
TELEGRAM_SMTP_PORT=587
TELEGRAM_SMTP_USER=ups@romfast.ro
TELEGRAM_SMTP_PASSWORD=your_smtp_password_here
TELEGRAM_SMTP_FROM_EMAIL=ups@romfast.ro
TELEGRAM_SMTP_FROM_NAME=ROA2WEB
TELEGRAM_SMTP_USE_TLS=true
# Email Retry Settings
TELEGRAM_EMAIL_MAX_RETRIES=3
TELEGRAM_EMAIL_RETRY_DELAY=2.0
# ============================================================================
# TELEGRAM MODULE - DATABASE (SQLite for bot data)
# ============================================================================
# Separate SQLite database for Telegram bot auth codes and sessions
TELEGRAM_SQLITE_DATABASE_PATH=data/telegram/telegram.db

View File

@@ -0,0 +1,212 @@
# Environment Configuration Guide
## Overview
The unified backend uses environment-specific configuration files that are automatically loaded by startup scripts.
**SECURITY**: All `.env*` files (except `.env*.example`) contain real credentials and are **NEVER committed to git**.
## File Structure
```
backend/
├── .env.prod.example # Production template (COMMITTED - no credentials)
├── .env.test.example # Test template (COMMITTED - no credentials)
├── .env.prod.example # Production template (COMMITTED - no credentials)
├── .env.example # Generic template (COMMITTED)
├── .env.prod # Production config (IGNORED - real credentials)
├── .env.test # Test config (IGNORED - real credentials)
├── .env.prod # Production config (IGNORED - real credentials)
└── .env # Active config (IGNORED - auto-generated)
```
## First-Time Setup
### Production
```bash
# 1. Copy template
cp backend/.env.prod.example backend/.env.prod
# 2. Edit with your credentials
vim backend/.env.prod
# 3. Fill in:
# - ORACLE_PASSWORD
# - JWT_SECRET_KEY (generate with: python3 -c "import secrets; print(secrets.token_urlsafe(32))")
# - AUTH_SESSION_SECRET (generate with: python3 -c "import secrets; print(secrets.token_urlsafe(32))")
# - TELEGRAM_BOT_TOKEN (from @BotFather)
# - SMTP_PASSWORD
# 4. Start
./start.sh prod
```
### Test
```bash
# Same process with .env.test
cp backend/.env.test.example backend/.env.test
vim backend/.env.test
# Fill in TEST credentials (separate from dev!)
./start.sh test
```
### Production
```bash
# Same process with .env.prod
cp backend/.env.prod.example backend/.env.prod
vim backend/.env.prod
# Fill in PRODUCTION credentials (generate NEW secrets!)
./start-backend.sh start
```
## How It Works
### Production
```bash
./start.sh prod # Checks for .env.prod → copies to .env → starts backend
```
### Test
```bash
./start.sh test # Checks for .env.test → copies to .env → starts backend
```
### Production
```bash
# Manual setup (one-time)
cp .env.prod.example .env.prod
vim .env.prod # Fill in credentials
# Then start
./start-backend.sh start
```
## Important Rules
### ✅ DO
- Copy `.env.*.example` to `.env.*` and fill in real credentials
- Edit `.env.prod` for production changes
- Edit `.env.test` for test environment changes
- Edit `.env.prod` for production
- Generate **new** secrets for each environment
- Keep `.env.prod`, `.env.test`, `.env.prod` **local only** (never commit!)
### ❌ DON'T
- Don't commit `.env`, `.env.prod`, `.env.test`, or `.env.prod` (they're in .gitignore)
- Don't manually edit `.env` (it's auto-generated!)
- Don't use same secrets across environments
- Don't share credentials via git (use secure channels)
- Don't put real credentials in `.env*.example` files
## Environment Differences
| Setting | .env.prod | .env.test | .env.prod |
|---------|----------|-----------|-----------|
| Oracle SID | `ROA` | `roa` | `ROA` |
| JWT Expire | 30 min | 480 min | 30 min |
| DEBUG | `true` | `true` | `false` |
| Cache DB | `roa2web_cache.db` | `roa2web_cache_test.db` | `roa2web_cache_prod.db` |
| Receipts DB | `receipts_dev.db` | `receipts_test.db` | `receipts_prod.db` |
| Telegram DB | `telegram.db` | `telegram_test.db` | `telegram_prod.db` |
## Security Notes
### Template Files (.env.*.example)
These contain **placeholders only**:
- ✅ Safe to commit to git
- ✅ Shared across team
- ✅ No real credentials
- 📖 Used as reference for first-time setup
### Actual Config Files (.env.prod, .env.test, .env.prod)
These contain **real credentials**:
-**NEVER commit to git** (in .gitignore)
- ❌ Never share via email/chat
- ✅ Keep local only
- ✅ Generate unique secrets per environment
- 🔐 Share securely if needed (encrypted vault, 1Password, etc.)
### Active Config (.env)
This is **auto-generated** and **ignored by git**:
- ❌ Never commit to git
- 🔄 Auto-overwritten by startup scripts
- 📝 Edit source files (.env.prod, .env.test) instead
## Generating Secrets
For `JWT_SECRET_KEY` and `AUTH_SESSION_SECRET`:
```bash
python3 -c "import secrets; print(secrets.token_urlsafe(32))"
```
Generate **different** secrets for dev, test, and production!
## Quick Reference
### First Time Setup
```bash
# 1. Copy template
cp backend/.env.prod.example backend/.env.prod
# 2. Fill credentials
vim backend/.env.prod
# 3. Start
./start.sh prod
```
### Changing Configuration
```bash
# 1. Edit source file
vim backend/.env.prod
# 2. Restart to apply
./start.sh prod
```
### Production Deployment
```bash
# 1. Copy template
cp backend/.env.prod.example backend/.env.prod
# 2. Fill in PRODUCTION values
vim backend/.env.prod
# 3. Generate NEW secrets
python3 -c "import secrets; print(secrets.token_urlsafe(32))"
# 4. Start backend
./start-backend.sh start
```
## Troubleshooting
### "Wrong database" error
Check that you're using the correct startup script:
- Production: `./start.sh prod` (uses `.env.prod`)
- Test: `./start.sh test` (uses `.env.test`)
### ".env.prod not found" error
First-time setup required:
```bash
cp backend/.env.prod.example backend/.env.prod
vim backend/.env.prod # Fill in your credentials
```
### Changes not taking effect
The `.env` file is regenerated on each start. Edit the source file (`.env.prod` or `.env.test`) instead.
### Checking what will be committed
```bash
git status backend/.env*
# Should show:
# modified: .env.prod.example (if you changed template)
# nothing else!
```
## Team Sharing
**Templates only** are committed to git:
- Share configuration structure via `.env*.example`
- Each developer creates their own `.env.prod` from template
- Never commit actual credentials
- Use secure channels for sharing sensitive values (1Password, encrypted vault, etc.)

View File

@@ -0,0 +1,102 @@
# Quick Environment Reference
## 🔒 SECURITY FIRST
**All `.env*` files (except `.env*.example`) contain real credentials and are NEVER committed to git!**
## 🚀 First-Time Setup
```bash
# 1. Copy template with real credentials
cp backend/.env.prod.example backend/.env.prod
# 2. Edit with YOUR credentials
vim backend/.env.prod
# 3. Fill in the placeholders:
# - ORACLE_PASSWORD
# - JWT_SECRET_KEY
# - AUTH_SESSION_SECRET
# - TELEGRAM_BOT_TOKEN
# - SMTP_PASSWORD
# 4. Start production
./start.sh prod
```
## 📋 Daily Usage
```bash
# Production (uses .env.prod automatically)
./start.sh prod
# Test Environment (uses .env.test automatically)
./start.sh test
# Quick Restart (uses existing .env)
./start-backend.sh restart
```
## ✏️ Changing Configuration
```bash
# 1. Edit the source file (NOT .env!)
vim backend/.env.prod # Production
vim backend/.env.test # Test
# 2. Restart to apply changes
./start.sh prod
```
## 📁 Which File to Edit?
| You Want To... | Edit This File |
|----------------|----------------|
| Change dev database password | `backend/.env.prod` |
| Update test server settings | `backend/.env.test` |
| Add new environment variable | Templates: `.env*.example` + your `.env.prod`/`.env.test` |
| Create production config | Copy `.env.prod.example` to `.env.prod` and fill secrets |
## 🔑 Generating Secrets
```bash
# For JWT_SECRET_KEY and AUTH_SESSION_SECRET
python3 -c "import secrets; print(secrets.token_urlsafe(32))"
```
**Generate DIFFERENT secrets for each environment (dev, test, prod)!**
## ⚠️ Important
- **Never edit** `backend/.env` directly (it's auto-generated!)
- **Always edit** `backend/.env.prod` or `.env.test`
- **Never commit** `.env`, `.env.prod`, `.env.test`, `.env.prod`
- **Only commit** `.env*.example` (templates with placeholders)
- Restart after changes for them to take effect
## 🛡️ Git Behavior
| File | Git Status | Contains |
|------|-----------|----------|
| `.env.prod.example` | ✅ Committed | Template (placeholders) |
| `.env.test.example` | ✅ Committed | Template (placeholders) |
| `.env.prod.example` | ✅ Committed | Template (placeholders) |
| `.env.example` | ✅ Committed | Generic template |
| `.env.prod` | ❌ Ignored | **Real dev credentials** |
| `.env.test` | ❌ Ignored | **Real test credentials** |
| `.env.prod` | ❌ Ignored | **Real prod credentials** |
| `.env` | ❌ Ignored | Auto-generated (current) |
## ✅ Quick Check
```bash
# See what git will commit
git status backend/.env*
# Should show ONLY .env*.example files
# If .env.prod or .env.test appear, they're NOT properly ignored!
```
## 📖 More Info
See `backend/ENV-SETUP.md` for complete documentation.

View File

@@ -0,0 +1,168 @@
@echo off
setlocal enabledelayedexpansion
cd /d "%~dp0"
REM Parse command line arguments for worker counts
REM Usage: TEST-OCR-WINDOWS.bat [worker_counts...]
REM Examples:
REM TEST-OCR-WINDOWS.bat -> tests 1,2,3 workers (default)
REM TEST-OCR-WINDOWS.bat 1 -> tests only 1 worker
REM TEST-OCR-WINDOWS.bat 3 6 -> tests 3 and 6 workers
REM TEST-OCR-WINDOWS.bat 1 2 3 4 5 6 -> tests all
set "WORKER_LIST=%*"
if "%WORKER_LIST%"=="" set "WORKER_LIST=1 2 3"
echo.
echo ==========================================
echo OCR Benchmark - Windows (Workers: %WORKER_LIST%)
echo ==========================================
echo.
REM Check if Poppler is installed
where pdftoppm >nul 2>&1
if errorlevel 1 (
echo Checking for Poppler...
if exist "E:\poppler" (
for /r "E:\poppler" %%i in (pdftoppm.exe) do (
set "POPPLER_BIN=%%~dpi"
goto :found_poppler
)
)
echo.
echo ERROR: Poppler not found!
pause
exit /b 1
)
:found_poppler
if defined POPPLER_BIN (
echo Found Poppler at: %POPPLER_BIN%
set "PATH=%POPPLER_BIN%;%PATH%"
)
REM Check venv
if not exist "venv-win\Scripts\python.exe" (
echo ERROR: venv-win not found!
echo Run: python -m venv venv-win
echo Then: venv-win\Scripts\pip install -r requirements.txt
pause
exit /b 1
)
REM Set common environment
set JWT_SECRET_KEY=generate_with_secrets_token_urlsafe_32
set ORACLE_HOST=10.0.20.121
set ORACLE_PORT=1521
set ORACLE_USER=CONTAFIN_ORACLE
set ORACLE_PASSWORD=ROMFASTSOFT
set ORACLE_SERVICE_NAME=ROA
set OCR_ENABLE_PADDLEOCR=false
set OCR_ENABLE_TESSERACT=false
set OCR_DEFAULT_ENGINE=hybrid-doctr
set OCR_MAX_TASKS_PER_CHILD=0
set LOG_LEVEL=WARNING
REM Results file with timestamp
for /f "tokens=2 delims==" %%I in ('wmic os get localdatetime /value') do set datetime=%%I
set RESULTS_FILE=ocr_benchmark_%datetime:~0,8%_%datetime:~8,4%.json
echo Results will be saved to: %RESULTS_FILE%
echo.
REM Delete old results file if exists
if exist "%RESULTS_FILE%" del "%RESULTS_FILE%"
REM Run tests with specified workers
for %%W in (%WORKER_LIST%) do (
call :run_test %%W
)
goto :show_summary
:run_test
set WORKERS=%1
echo.
echo ############################################################
echo STARTING TEST WITH %WORKERS% WORKER(S)
echo ############################################################
echo.
REM Kill existing processes on port 8006
echo Cleaning up old processes...
for /f "tokens=5" %%a in ('netstat -ano ^| findstr :8006 ^| findstr LISTENING 2^>nul') do (
taskkill /F /PID %%a >nul 2>&1
)
taskkill /F /FI "WINDOWTITLE eq ROA2WEB Backend*" >nul 2>&1
timeout /t 3 >nul
REM Set workers count
set OCR_WORKERS=%WORKERS%
echo Starting backend with %WORKERS% OCR worker(s)...
REM Start backend in a new minimized window with all OCR env vars
start /min "ROA2WEB Backend %WORKERS% workers" cmd /c "set OCR_WORKERS=%WORKERS%&& set OCR_ENABLE_PADDLEOCR=false&& set OCR_ENABLE_TESSERACT=false&& set OCR_DEFAULT_ENGINE=hybrid-doctr&& set LOG_LEVEL=WARNING&& venv-win\Scripts\python.exe -m uvicorn main:app --host 0.0.0.0 --port 8006 --workers 1 2>&1"
REM Wait for backend to be ready
echo Waiting for backend to start...
set attempts=0
:wait_loop
timeout /t 3 >nul
set /a attempts+=1
curl -s http://localhost:8006/health >nul 2>&1
if errorlevel 1 (
if !attempts! lss 40 (
echo Waiting... !attempts!/40
goto :wait_loop
)
echo ERROR: Backend failed to start!
goto :eof
)
echo Backend is ready!
REM Wait for OCR warmup
echo Waiting for OCR worker warmup (30s)...
timeout /t 30 >nul
echo.
echo Running OCR test with %WORKERS% worker(s)...
echo.
venv-win\Scripts\python.exe ..\tests\ocr-validation\test_receipts_parallel_windows.py --port 8006 --workers %WORKERS% --output %RESULTS_FILE%
REM Stop backend
echo.
echo Stopping backend...
taskkill /F /FI "WINDOWTITLE eq ROA2WEB Backend*" >nul 2>&1
for /f "tokens=5" %%a in ('netstat -ano ^| findstr :8006 ^| findstr LISTENING 2^>nul') do (
taskkill /F /PID %%a >nul 2>&1
)
REM Wait for memory to be released
echo Releasing memory (10s)...
timeout /t 10 >nul
goto :eof
:show_summary
echo.
echo ############################################################
echo ALL TESTS COMPLETE
echo ############################################################
echo.
echo Results saved to: %RESULTS_FILE%
echo.
REM Show summary from results file
if exist "%RESULTS_FILE%" (
echo BENCHMARK SUMMARY:
echo ------------------
venv-win\Scripts\python.exe -c "import json; data=json.load(open('%RESULTS_FILE%')); print(); [print(f\" {r['workers']} worker(s): {r['total_time']:.1f}s total, {r['avg_time']:.1f}s avg, {r.get('peak_memory_mb', 0):.0f}MB peak, {r['successful']}/{r['submitted']} success\") for r in data]"
echo.
)
echo Press any key to exit...
pause >nul
endlocal

View File

@@ -0,0 +1,296 @@
"""
Unified Configuration for ROA2WEB Backend
Consolidates settings from Reports, Data Entry, and Telegram modules
"""
import os
import json
import logging
from pathlib import Path
from typing import List, Optional
from pydantic_settings import BaseSettings
from pydantic import BaseModel
from functools import lru_cache
logger = logging.getLogger(__name__)
class OracleServerConfig(BaseModel):
"""Configuration for a single Oracle server instance."""
id: str # Unique identifier (e.g., "romfast", "client_a")
name: str # Human-readable name (e.g., "Romfast - Producție")
host: str = "localhost"
port: int = 1521
user: str
password: str
sid: Optional[str] = None
service_name: Optional[str] = None
def get_dsn(self) -> str:
"""Build DSN string for this server."""
if self.service_name:
return f"{self.host}:{self.port}/{self.service_name}"
elif self.sid:
return f"{self.host}:{self.port}:{self.sid}"
else:
return f"{self.host}:{self.port}/ROA"
class UnifiedSettings(BaseSettings):
"""Unified application settings for all modules."""
# ============================================================================
# GENERAL APPLICATION SETTINGS
# ============================================================================
app_name: str = "ROA2WEB Unified Backend"
app_version: str = "1.0.0"
debug: bool = False
api_host: str = "0.0.0.0"
api_port: int = 8000
# ============================================================================
# ORACLE DATABASE (Shared by all modules)
# ============================================================================
# Legacy single-server configuration (backward compatible)
oracle_user: str = ""
oracle_password: str = ""
oracle_host: str = "localhost"
oracle_port: int = 1526
oracle_sid: str = "ROA"
# ============================================================================
# MULTI-ORACLE SERVER CONFIGURATION (Optional)
# ============================================================================
# JSON array of server configs. If not set, uses legacy single-server config.
# Example: ORACLE_SERVERS='[{"id": "romfast", "name": "Romfast", "host": "localhost", "port": 1521, "user": "USER", "password": "PASS", "sid": "ROA"}]'
oracle_servers: Optional[str] = None # Raw JSON string from env
# Parsed server configurations (populated in model_post_init)
_oracle_servers_parsed: List[OracleServerConfig] = []
def model_post_init(self, __context) -> None:
"""Parse ORACLE_SERVERS JSON and build server list.
Oracle passwords are loaded from:
1. secrets/{server_id}.oracle_pass file (preferred, more secure)
2. password field in ORACLE_SERVERS JSON (fallback)
"""
servers = []
secrets_dir = Path(__file__).parent / "secrets"
if self.oracle_servers:
# Parse multi-server JSON configuration
try:
servers_data = json.loads(self.oracle_servers)
if not isinstance(servers_data, list):
raise ValueError("ORACLE_SERVERS must be a JSON array")
for server_data in servers_data:
server_id = server_data.get("id", "default")
# Try to load password from secrets file
pass_file = secrets_dir / f"{server_id}.oracle_pass"
if pass_file.exists():
server_data["password"] = pass_file.read_text().strip()
logger.debug(f"Loaded Oracle password for '{server_id}' from {pass_file}")
elif "password" not in server_data:
logger.warning(f"No password found for server '{server_id}' - check secrets/{server_id}.oracle_pass")
servers.append(OracleServerConfig(**server_data))
logger.info(f"Loaded {len(servers)} Oracle servers from ORACLE_SERVERS config")
for srv in servers:
logger.info(f" - {srv.id}: {srv.name} ({srv.host}:{srv.port})")
except json.JSONDecodeError as e:
logger.error(f"Failed to parse ORACLE_SERVERS JSON: {e}")
raise ValueError(f"Invalid ORACLE_SERVERS JSON format: {e}")
else:
# Backward compatibility: build default server from legacy config
if self.oracle_user:
# Try to load password from secrets file
password = self.oracle_password
pass_file = secrets_dir / "default.oracle_pass"
if pass_file.exists():
password = pass_file.read_text().strip()
logger.debug(f"Loaded Oracle password from {pass_file}")
default_server = OracleServerConfig(
id="default",
name="Default Server",
host=self.oracle_host,
port=self.oracle_port,
user=self.oracle_user,
password=password,
sid=self.oracle_sid,
)
servers.append(default_server)
logger.info("Using legacy single-server Oracle configuration (ORACLE_USER/HOST/etc)")
logger.info(f" - default: {default_server.host}:{default_server.port}/{default_server.sid}")
object.__setattr__(self, '_oracle_servers_parsed', servers)
def get_oracle_servers(self) -> List[OracleServerConfig]:
"""Get list of configured Oracle servers."""
return self._oracle_servers_parsed
def get_oracle_server(self, server_id: str) -> Optional[OracleServerConfig]:
"""Get a specific Oracle server by ID."""
for server in self._oracle_servers_parsed:
if server.id == server_id:
return server
return None
def get_default_oracle_server(self) -> Optional[OracleServerConfig]:
"""Get the default Oracle server (first in list or 'default')."""
if not self._oracle_servers_parsed:
return None
# Try to find server with id='default', otherwise return first
for server in self._oracle_servers_parsed:
if server.id == "default":
return server
return self._oracle_servers_parsed[0]
# ============================================================================
# JWT AUTHENTICATION (Shared by all modules)
# ============================================================================
jwt_secret_key: str = "change-me-in-production"
jwt_algorithm: str = "HS256"
access_token_expire_minutes: int = 30
refresh_token_expire_days: int = 7
# ============================================================================
# SESSION SECURITY - EMAIL 2FA (Telegram module)
# ============================================================================
auth_session_secret: str = "change-me-in-production"
# ============================================================================
# CORS
# ============================================================================
cors_origins: str = "http://localhost:3000,http://localhost:5173"
# ============================================================================
# REPORTS MODULE - CACHE CONFIGURATION
# ============================================================================
reports_cache_enabled: bool = True
reports_cache_type: str = "hybrid"
reports_cache_sqlite_path: str = "./data/cache/roa2web_cache.db"
reports_cache_memory_max_size: int = 1000
reports_cache_default_ttl: int = 900
# Cache TTL per type (seconds)
reports_cache_ttl_schema: int = 86400
reports_cache_ttl_companies: int = 1800
reports_cache_ttl_dashboard_summary: int = 1800
reports_cache_ttl_dashboard_trends: int = 1800
reports_cache_ttl_invoices: int = 600
reports_cache_ttl_invoices_summary: int = 900
reports_cache_ttl_treasury: int = 600
# Cache maintenance
reports_cache_cleanup_interval: int = 3600
reports_cache_auto_invalidate: bool = False
reports_cache_check_interval: int = 300
reports_cache_track_performance: bool = True
reports_cache_benchmark_on_startup: bool = False
# ============================================================================
# DATA ENTRY MODULE - CONFIGURATION
# ============================================================================
data_entry_sqlite_database_path: str = "data/receipts/receipts.db"
data_entry_upload_path: str = "data/receipts/uploads"
data_entry_max_upload_size_mb: int = 10
data_entry_allowed_mime_types: List[str] = [
"image/jpeg",
"image/png",
"image/gif",
"image/webp",
"application/pdf",
]
# ============================================================================
# TELEGRAM MODULE - BOT CONFIGURATION
# ============================================================================
telegram_bot_token: str = ""
telegram_smtp_host: str = ""
telegram_smtp_port: int = 587
telegram_smtp_user: str = ""
telegram_smtp_password: str = ""
telegram_smtp_from_email: str = ""
telegram_smtp_from_name: str = "ROA2WEB"
telegram_smtp_use_tls: bool = True
telegram_email_max_retries: int = 3
telegram_email_retry_delay: float = 2.0
telegram_sqlite_database_path: str = "data/telegram/telegram.db"
class Config:
env_file = ".env"
env_file_encoding = "utf-8"
extra = "ignore"
case_sensitive = False
# ============================================================================
# COMPUTED PROPERTIES
# ============================================================================
@property
def oracle_dsn(self) -> str:
"""Get Oracle DSN string."""
return f"{self.oracle_host}:{self.oracle_port}/{self.oracle_sid}"
@property
def cors_origins_list(self) -> List[str]:
"""Get CORS origins as list."""
return [origin.strip() for origin in self.cors_origins.split(",")]
# Data Entry properties
@property
def data_entry_database_url(self) -> str:
"""Get SQLite database URL for async (Data Entry)."""
# Resolve to absolute path for Windows/IIS compatibility
abs_path = Path(self.data_entry_sqlite_database_path).resolve()
return f"sqlite+aiosqlite:///{abs_path}"
@property
def data_entry_sync_database_url(self) -> str:
"""Get SQLite database URL for sync operations (Alembic)."""
# Resolve to absolute path for Windows/IIS compatibility
abs_path = Path(self.data_entry_sqlite_database_path).resolve()
return f"sqlite:///{abs_path}"
@property
def data_entry_upload_path_resolved(self) -> Path:
"""Get resolved upload path."""
path = Path(self.data_entry_upload_path)
path.mkdir(parents=True, exist_ok=True)
return path
@property
def data_entry_max_upload_size_bytes(self) -> int:
"""Get max upload size in bytes."""
return self.data_entry_max_upload_size_mb * 1024 * 1024
# Reports cache properties
@property
def reports_cache_sqlite_path_resolved(self) -> Path:
"""Get resolved cache SQLite path."""
path = Path(self.reports_cache_sqlite_path)
path.parent.mkdir(parents=True, exist_ok=True)
return path
# Telegram properties
@property
def telegram_sqlite_path_resolved(self) -> Path:
"""Get resolved Telegram SQLite path."""
path = Path(self.telegram_sqlite_database_path)
path.parent.mkdir(parents=True, exist_ok=True)
return path
@lru_cache()
def get_settings() -> UnifiedSettings:
"""Get cached settings instance."""
return UnifiedSettings()
# Convenience instance
settings = get_settings()

View File

@@ -0,0 +1,45 @@
# Backend Runtime Data
This directory contains runtime data generated by the unified backend.
## Directory Structure
```
data/
├── cache/ # Reports module cache (hybrid L1+L2)
│ └── *.db # SQLite L2 cache database
├── receipts/ # Data Entry module data
│ ├── *.db # SQLite receipts database
│ └── uploads/ # User-uploaded files (receipts, attachments)
└── telegram/ # Telegram bot data
└── *.db # SQLite bot auth/session database
```
## Git Behavior
- **Ignored**: All `*.db` files and `uploads/` contents
- **Committed**: Only `.gitkeep` files to preserve directory structure
## Environment-Specific Databases
Different environments use separate databases:
- **Development** (`.env.prod`):
- Cache: `roa2web_cache.db`
- Receipts: `receipts_dev.db`
- Telegram: `telegram.db`
- **Test** (`.env.test`):
- Cache: `roa2web_cache_test.db`
- Receipts: `receipts_test.db`
- Telegram: `telegram_test.db`
- **Production** (`.env.prod`):
- Cache: `roa2web_cache_prod.db`
- Receipts: `receipts_prod.db`
- Telegram: `telegram_prod.db`
## Auto-Created
All databases and directories are created automatically on first run.
No manual setup required.

Binary file not shown.

After

Width:  |  Height:  |  Size: 299 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 323 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 299 KiB

Some files were not shown because too many files have changed in this diff Show More