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:
293
reports-app/telegram-bot/app/main.py
Normal file
293
reports-app/telegram-bot/app/main.py
Normal file
@@ -0,0 +1,293 @@
|
||||
"""
|
||||
Main entry point for ROA2WEB Telegram Bot
|
||||
|
||||
This bot provides access to the ROA2WEB ERP system through Telegram
|
||||
using direct command handlers for financial data queries.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
import os
|
||||
from pathlib import Path
|
||||
from dotenv import load_dotenv
|
||||
import uvicorn
|
||||
from threading import Thread
|
||||
|
||||
# Telegram imports
|
||||
from telegram.ext import (
|
||||
Application,
|
||||
CommandHandler,
|
||||
CallbackQueryHandler,
|
||||
MessageHandler,
|
||||
filters
|
||||
)
|
||||
|
||||
# Import database initialization
|
||||
from app.db import init_database, cleanup_expired_codes, cleanup_expired_sessions
|
||||
|
||||
# Import bot handlers
|
||||
from app.bot.handlers import (
|
||||
start_command,
|
||||
help_command,
|
||||
clear_command,
|
||||
companies_command,
|
||||
unlink_command,
|
||||
selectcompany_command,
|
||||
dashboard_command,
|
||||
sold_command,
|
||||
facturi_command,
|
||||
trezorerie_command,
|
||||
# FAZA 3: New command handlers with button interface
|
||||
menu_command,
|
||||
trezorerie_casa_command,
|
||||
trezorerie_banca_command,
|
||||
clienti_command,
|
||||
furnizori_command,
|
||||
evolutie_command,
|
||||
# Text message handlers
|
||||
handle_text_message,
|
||||
# FAZA 4: Callback and error handlers
|
||||
button_callback,
|
||||
error_handler
|
||||
)
|
||||
|
||||
# Import internal API
|
||||
from app.internal_api import internal_api
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Load environment variables
|
||||
env_path = Path(__file__).parent.parent / '.env'
|
||||
load_dotenv(env_path)
|
||||
|
||||
# Environment variables
|
||||
TELEGRAM_BOT_TOKEN = os.getenv('TELEGRAM_BOT_TOKEN')
|
||||
BACKEND_URL = os.getenv('BACKEND_URL', 'http://localhost:8001')
|
||||
INTERNAL_API_PORT = int(os.getenv('INTERNAL_API_PORT', '8002'))
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# TELEGRAM BOT SETUP
|
||||
# ============================================================================
|
||||
|
||||
def create_telegram_application() -> Application:
|
||||
"""
|
||||
Create and configure the Telegram bot application.
|
||||
|
||||
Returns:
|
||||
Application: Configured Telegram application
|
||||
"""
|
||||
logger.info("Creating Telegram application...")
|
||||
|
||||
# Create application
|
||||
application = Application.builder().token(TELEGRAM_BOT_TOKEN).build()
|
||||
|
||||
# Register essential command handlers
|
||||
application.add_handler(CommandHandler("start", start_command))
|
||||
application.add_handler(CommandHandler("menu", menu_command))
|
||||
application.add_handler(CommandHandler("help", help_command))
|
||||
application.add_handler(CommandHandler("unlink", unlink_command))
|
||||
|
||||
# =========================================================================
|
||||
# LEGACY COMMAND HANDLERS (kept for backwards compatibility, hidden from help)
|
||||
# =========================================================================
|
||||
# NOTE: These commands are redundant with the button interface.
|
||||
# They're kept for users who already know them, but we push buttons in help.
|
||||
# Consider removing completely if migration is successful.
|
||||
|
||||
application.add_handler(CommandHandler("clear", clear_command))
|
||||
application.add_handler(CommandHandler("companies", companies_command))
|
||||
application.add_handler(CommandHandler("selectcompany", selectcompany_command))
|
||||
application.add_handler(CommandHandler("dashboard", dashboard_command))
|
||||
application.add_handler(CommandHandler("sold", sold_command))
|
||||
application.add_handler(CommandHandler("facturi", facturi_command))
|
||||
application.add_handler(CommandHandler("trezorerie", trezorerie_command))
|
||||
application.add_handler(CommandHandler("trezorerie_casa", trezorerie_casa_command))
|
||||
application.add_handler(CommandHandler("trezorerie_banca", trezorerie_banca_command))
|
||||
application.add_handler(CommandHandler("clienti", clienti_command))
|
||||
application.add_handler(CommandHandler("furnizori", furnizori_command))
|
||||
application.add_handler(CommandHandler("evolutie", evolutie_command))
|
||||
|
||||
# Text message handler (for direct code input and future NLP)
|
||||
# IMPORTANT: This must be registered BEFORE CallbackQueryHandler
|
||||
# filters.TEXT & ~filters.COMMAND ensures we only process non-command text messages
|
||||
application.add_handler(MessageHandler(
|
||||
filters.TEXT & ~filters.COMMAND,
|
||||
handle_text_message
|
||||
))
|
||||
|
||||
# FAZA 4: Register callback query handler (for inline buttons)
|
||||
application.add_handler(CallbackQueryHandler(button_callback))
|
||||
|
||||
# Register error handler
|
||||
application.add_error_handler(error_handler)
|
||||
|
||||
logger.info("Telegram application configured with all handlers")
|
||||
|
||||
return application
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# INTERNAL API SERVER
|
||||
# ============================================================================
|
||||
|
||||
def run_internal_api():
|
||||
"""
|
||||
Run the internal FastAPI server in a separate thread.
|
||||
|
||||
This API handles communication from the backend (saving auth codes).
|
||||
"""
|
||||
logger.info(f"Starting internal API on port {INTERNAL_API_PORT}...")
|
||||
|
||||
uvicorn.run(
|
||||
internal_api,
|
||||
host="0.0.0.0",
|
||||
port=INTERNAL_API_PORT,
|
||||
log_level="info"
|
||||
)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# STARTUP/SHUTDOWN
|
||||
# ============================================================================
|
||||
|
||||
async def startup():
|
||||
"""
|
||||
Initialize the bot application on startup.
|
||||
"""
|
||||
logger.info("🚀 ROA2WEB Telegram Bot - Starting up...")
|
||||
|
||||
# Initialize database
|
||||
try:
|
||||
logger.info("Initializing SQLite database...")
|
||||
await init_database()
|
||||
logger.info("✅ Database initialized successfully")
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Failed to initialize database: {e}")
|
||||
raise
|
||||
|
||||
# Cleanup expired data
|
||||
try:
|
||||
logger.info("Cleaning up expired data...")
|
||||
expired_codes = await cleanup_expired_codes()
|
||||
expired_sessions = await cleanup_expired_sessions()
|
||||
logger.info(f"✅ Cleanup complete: {expired_codes} codes, {expired_sessions} sessions removed")
|
||||
except Exception as e:
|
||||
logger.warning(f"⚠️ Cleanup failed (non-critical): {e}")
|
||||
|
||||
logger.info("✅ Startup complete")
|
||||
|
||||
|
||||
async def shutdown():
|
||||
"""
|
||||
Clean up resources on shutdown.
|
||||
"""
|
||||
logger.info("👋 ROA2WEB Telegram Bot - Shutting down...")
|
||||
logger.info("✅ Shutdown complete")
|
||||
|
||||
|
||||
async def scheduled_cleanup():
|
||||
"""
|
||||
Background task to periodically clean up expired data.
|
||||
Runs every hour to remove expired auth codes and sessions.
|
||||
"""
|
||||
while True:
|
||||
try:
|
||||
await asyncio.sleep(3600) # Sleep for 1 hour
|
||||
logger.info("🧹 Running scheduled cleanup...")
|
||||
expired_codes = await cleanup_expired_codes()
|
||||
expired_sessions = await cleanup_expired_sessions()
|
||||
logger.info(f"✅ Scheduled cleanup: {expired_codes} codes, {expired_sessions} sessions removed")
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Error in scheduled cleanup: {e}")
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# MAIN APPLICATION
|
||||
# ============================================================================
|
||||
|
||||
async def main():
|
||||
"""
|
||||
Main application entry point.
|
||||
|
||||
Runs both the Telegram bot and internal API server concurrently.
|
||||
"""
|
||||
try:
|
||||
# Run startup
|
||||
await startup()
|
||||
|
||||
# Create Telegram application
|
||||
telegram_app = create_telegram_application()
|
||||
|
||||
# Start internal API in a separate thread
|
||||
api_thread = Thread(target=run_internal_api, daemon=True)
|
||||
api_thread.start()
|
||||
logger.info(f"✅ Internal API started on port {INTERNAL_API_PORT}")
|
||||
|
||||
# Start scheduled cleanup task in background
|
||||
cleanup_task = asyncio.create_task(scheduled_cleanup())
|
||||
logger.info("✅ Scheduled cleanup task started")
|
||||
|
||||
# Initialize and start Telegram bot
|
||||
logger.info("🤖 Starting Telegram bot polling...")
|
||||
await telegram_app.initialize()
|
||||
await telegram_app.start()
|
||||
await telegram_app.updater.start_polling(drop_pending_updates=True)
|
||||
|
||||
logger.info("✅ Telegram bot is now running and polling for updates")
|
||||
logger.info(f"📱 Bot ready to receive messages at @{(await telegram_app.bot.get_me()).username}")
|
||||
logger.info("🎯 Bot is operational with direct command handlers!")
|
||||
|
||||
# Keep running until interrupted
|
||||
await asyncio.Event().wait()
|
||||
|
||||
except KeyboardInterrupt:
|
||||
logger.info("⚠️ Received interrupt signal")
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Fatal error: {e}", exc_info=True)
|
||||
raise
|
||||
finally:
|
||||
# Stop Telegram bot gracefully
|
||||
try:
|
||||
if 'telegram_app' in locals():
|
||||
logger.info("Stopping Telegram bot...")
|
||||
await telegram_app.updater.stop()
|
||||
await telegram_app.stop()
|
||||
await telegram_app.shutdown()
|
||||
logger.info("✅ Telegram bot stopped")
|
||||
except Exception as e:
|
||||
logger.error(f"Error stopping Telegram bot: {e}")
|
||||
|
||||
await shutdown()
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# ENTRY POINT
|
||||
# ============================================================================
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Check required environment variables
|
||||
if not os.getenv('TELEGRAM_BOT_TOKEN'):
|
||||
logger.error("❌ TELEGRAM_BOT_TOKEN is required")
|
||||
logger.error("Please set it in .env file")
|
||||
exit(1)
|
||||
|
||||
# Display startup banner
|
||||
logger.info("=" * 60)
|
||||
logger.info(" ROA2WEB TELEGRAM BOT")
|
||||
logger.info(" Financial ERP Assistant with Direct Commands")
|
||||
logger.info("=" * 60)
|
||||
|
||||
# Run the main application
|
||||
try:
|
||||
asyncio.run(main())
|
||||
except KeyboardInterrupt:
|
||||
logger.info("👋 Application stopped by user")
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Application failed: {e}", exc_info=True)
|
||||
exit(1)
|
||||
Reference in New Issue
Block a user