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
119 lines
4.5 KiB
Python
119 lines
4.5 KiB
Python
"""
|
|
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() |