Initial commit: ROA2WEB - FastAPI + Vue.js + Telegram Bot

Modern ERP Reports Application with microservices architecture

Tech Stack:
- Backend: FastAPI + python-oracledb (Oracle DB integration)
- Frontend: Vue.js 3 + PrimeVue + Vite
- Telegram Bot: python-telegram-bot + SQLite
- Infrastructure: Shared database pool, JWT authentication, SSH tunnel

Features:
- FastAPI backend with async Oracle connection pool
- Vue.js 3 responsive frontend with PrimeVue components
- Telegram bot alternative interface
- Microservices architecture with shared components
- Complete deployment support (Linux Docker + Windows IIS)
- Comprehensive testing (Playwright E2E + pytest)

Repository Structure:
- reports-app/ - Main application (backend, frontend, telegram-bot)
- shared/ - Shared components (database pool, auth, utils)
- deployment/ - Deployment scripts (Linux & Windows)
- docs/ - Project documentation
- security/ - Security scanning and git hooks
This commit is contained in:
2025-10-25 14:55:08 +03:00
commit 6b13ffa183
237 changed files with 70035 additions and 0 deletions

124
shared/database/README.md Normal file
View File

@@ -0,0 +1,124 @@
# ROA2WEB Shared Database Pool
Sistem de pool de conexiuni Oracle partajat între toate microserviciile ROA2WEB.
## Componente
### 📦 oracle_pool.py
Clasa singleton `OraclePool` pentru gestionarea pool-ului de conexiuni Oracle.
### 📋 models.py
Modele Pydantic comune:
- `User` - Model pentru utilizatori
- `Company` - Model pentru firme/scheme Oracle
- `DatabaseConfig` - Configurare conexiune database
### ⚙️ config.py (în utils/)
Configurări partajate prin environment variables.
### ❌ exceptions.py (în utils/)
Exception handlers personalizate pentru ROA2WEB.
## Utilizare
### Inițializare în aplicații FastAPI
```python
from contextlib import asynccontextmanager
from fastapi import FastAPI
import sys
import os
# Import shared pool
sys.path.append(os.path.join(os.path.dirname(__file__), '../../../shared'))
from database.oracle_pool import oracle_pool
@asynccontextmanager
async def lifespan(app: FastAPI):
# Startup - inițializare pool
await oracle_pool.initialize()
print("📊 Oracle pool initialized")
yield
# Shutdown - închidere pool
await oracle_pool.close_pool()
print("📊 Oracle pool closed")
app = FastAPI(lifespan=lifespan)
```
### Utilizare conexiune în endpoint-uri
```python
from fastapi import APIRouter, HTTPException
from database.oracle_pool import oracle_pool
router = APIRouter()
@router.get("/companies")
async def get_companies():
try:
async with oracle_pool.get_connection() as conn:
with conn.cursor() as cursor:
cursor.execute("SELECT schema, firma FROM vdef_util_grup WHERE id_firma <> 0")
results = cursor.fetchall()
companies = []
for row in results:
companies.append({
"code": row[0],
"name": row[1]
})
return companies
except Exception as e:
raise HTTPException(status_code=500, detail=f"Database error: {str(e)}")
```
### Configurare Environment Variables
```bash
# Oracle Database
ORACLE_USER=your_oracle_username
ORACLE_PASSWORD=your_oracle_password
ORACLE_DSN=your_oracle_dsn
# Pool Settings
DB_MIN_CONNECTIONS=2
DB_MAX_CONNECTIONS=10
DB_CONNECTION_INCREMENT=1
# JWT (pentru autentificare)
JWT_SECRET_KEY=your-super-secret-key
ACCESS_TOKEN_EXPIRE_MINUTES=30
```
## Testare
Pentru a testa pool-ul de conexiuni:
```bash
cd roa2web/shared/database
python test_pool.py
```
**Notă**: Testul necesită configurarea variabilelor de environment pentru Oracle.
## Caracteristici
**Singleton Pattern** - O singură instanță de pool pentru toată aplicația
**Async Context Manager** - Gestionare automată a conexiunilor
**Connection Pooling** - Performanță optimizată prin reutilizarea conexiunilor
**Configurabil** - Setări flexibile prin environment variables
**Logging** - Urmărirea operațiilor de pool
**Error Handling** - Excepții personalizate pentru debugging
## Următorii Pași
👉 **ZIUA 3**: Implementarea sistemului JWT partajat (`shared/auth/`)
---
*Documentație generată pentru ROA2WEB Shared Database Pool - ZIUA 2* 🚀

View File

30
shared/database/models.py Normal file
View File

@@ -0,0 +1,30 @@
"""
Modele comune pentru toate aplicațiile ROA2WEB
"""
from pydantic import BaseModel, Field
from typing import List, Optional, Dict
from datetime import datetime
class Company(BaseModel):
"""Model pentru firma/schema Oracle"""
code: str = Field(description="Codul firmei (schema Oracle)")
name: str = Field(description="Numele firmei")
fiscal_code: Optional[str] = Field(description="Codul fiscal")
is_active: bool = Field(default=True, description="Firma activă")
class User(BaseModel):
"""Model pentru utilizator"""
username: str = Field(description="Numele utilizatorului")
email: Optional[str] = Field(description="Email utilizator")
companies: List[str] = Field(description="Lista codurilor firmelor la care are acces")
is_active: bool = Field(default=True, description="Utilizator activ")
last_login: Optional[datetime] = Field(description="Ultima autentificare")
class DatabaseConfig(BaseModel):
"""Configurare conexiune bază de date"""
user: str
password: str
dsn: str
min_connections: int = 2
max_connections: int = 10
increment: int = 1

View File

@@ -0,0 +1,119 @@
"""
Oracle Database Connection Pool - Shared între toate aplicațiile ROA2WEB
Folosește oracledb cu connection pooling pentru performance optimă
"""
import oracledb
import os
from contextlib import asynccontextmanager
from typing import Optional
import logging
logger = logging.getLogger(__name__)
class OraclePool:
"""
Singleton class pentru Oracle connection pool
Partajat între toate microservicele ROA2WEB
"""
_instance: Optional['OraclePool'] = None
_pool: Optional[oracledb.ConnectionPool] = None
def __new__(cls):
if cls._instance is None:
cls._instance = super(OraclePool, cls).__new__(cls)
return cls._instance
async def initialize(self, **config):
"""Inițializează pool-ul de conexiuni"""
if self._pool is None:
# Check if we have DSN or individual parameters
dsn = config.get('dsn', os.getenv('ORACLE_DSN'))
if dsn:
# Use DSN connection
self._pool = oracledb.create_pool(
user=config.get('user', os.getenv('ORACLE_USER')),
password=config.get('password', os.getenv('ORACLE_PASSWORD')),
dsn=dsn,
min=config.get('min_connections', 2),
max=config.get('max_connections', 10),
increment=config.get('increment', 1),
getmode=oracledb.POOL_GETMODE_WAIT
)
else:
# Use individual parameters (host, port, sid)
self._pool = oracledb.create_pool(
user=config.get('user', os.getenv('ORACLE_USER')),
password=config.get('password', os.getenv('ORACLE_PASSWORD')),
host=config.get('host', os.getenv('ORACLE_HOST', 'localhost')),
port=config.get('port', int(os.getenv('ORACLE_PORT', '1526'))),
sid=config.get('sid', os.getenv('ORACLE_SID', 'ROA')),
min=config.get('min_connections', 2),
max=config.get('max_connections', 10),
increment=config.get('increment', 1),
getmode=oracledb.POOL_GETMODE_WAIT
)
logger.info(f"Oracle pool created with {self._pool.opened} connections")
@asynccontextmanager
async def get_connection(self):
"""Context manager pentru obținerea unei conexiuni din pool"""
if self._pool is None:
raise RuntimeError("Pool not initialized. Call initialize() first.")
connection = None
try:
connection = self._pool.acquire()
logger.debug("Connection acquired from pool")
yield connection
finally:
if connection is not None:
connection.close()
logger.debug("Connection returned to pool")
async def execute_query(self, query: str, parameters=None):
"""
Execute a SQL query and return all results
Based on official Oracle python-oracledb patterns
"""
if self._pool is None:
raise RuntimeError("Pool not initialized. Call initialize() first.")
connection = None
try:
connection = self._pool.acquire()
logger.debug(f"Executing query: {query[:100]}...")
with connection.cursor() as cursor:
if parameters:
cursor.execute(query, parameters)
else:
cursor.execute(query)
# Check if this is a SELECT statement
if query.strip().upper().startswith('SELECT') or query.strip().upper().startswith('WITH'):
return cursor.fetchall()
else:
# For DML statements, return affected row count
connection.commit()
return cursor.rowcount
except Exception as e:
if connection:
connection.rollback()
logger.error(f"Query execution failed: {str(e)}")
raise
finally:
if connection is not None:
connection.close()
logger.debug("Connection returned to pool")
async def close_pool(self):
"""Închide pool-ul de conexiuni"""
if self._pool is not None:
self._pool.close()
self._pool = None
logger.info("Oracle pool closed")
# Instance globală pentru folosire în toate aplicațiile
oracle_pool = OraclePool()