import sqlite3 import os import secrets import hashlib from typing import Optional, Dict, List class DatabaseManager: def __init__(self, db_path=None): self.db_path = db_path or os.environ.get('DATABASE_PATH', 'tmc_customer_service.db') os.makedirs(os.path.dirname(self.db_path) or '.', exist_ok=True) self.init_database() def get_connection(self): conn = sqlite3.connect(self.db_path) conn.row_factory = sqlite3.Row return conn def init_database(self): with self.get_connection() as conn: c = conn.cursor() c.execute('''CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY AUTOINCREMENT, email TEXT UNIQUE NOT NULL, first_name TEXT NOT NULL, last_name TEXT NOT NULL, password_hash TEXT NOT NULL, salt TEXT NOT NULL, phone TEXT, company TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, is_active BOOLEAN DEFAULT 1, role TEXT DEFAULT 'user' )''') c.execute('''CREATE TABLE IF NOT EXISTS sessions ( id TEXT PRIMARY KEY, user_id INTEGER, ip_address TEXT, user_agent TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, expires_at TIMESTAMP NOT NULL, is_active BOOLEAN DEFAULT 1, FOREIGN KEY(user_id) REFERENCES users(id) )''') c.execute('''CREATE TABLE IF NOT EXISTS conversations ( id TEXT PRIMARY KEY, user_id INTEGER, session_id TEXT, title TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, is_active BOOLEAN DEFAULT 1, FOREIGN KEY(user_id) REFERENCES users(id) )''') c.execute('''CREATE TABLE IF NOT EXISTS messages ( id INTEGER PRIMARY KEY AUTOINCREMENT, conversation_id TEXT NOT NULL, role TEXT NOT NULL, content TEXT NOT NULL, timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP, model_used TEXT, response_time_ms INTEGER, FOREIGN KEY(conversation_id) REFERENCES conversations(id) )''') c.execute('''CREATE TABLE IF NOT EXISTS support_tickets ( id INTEGER PRIMARY KEY AUTOINCREMENT, ticket_number TEXT UNIQUE NOT NULL, user_id INTEGER NOT NULL, conversation_id TEXT, subject TEXT NOT NULL, description TEXT NOT NULL, category TEXT NOT NULL, priority TEXT DEFAULT 'medium', status TEXT DEFAULT 'open', assigned_agent TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, resolved_at TIMESTAMP, FOREIGN KEY(user_id) REFERENCES users(id) )''') c.execute('''CREATE TABLE IF NOT EXISTS ticket_updates ( id INTEGER PRIMARY KEY AUTOINCREMENT, ticket_id INTEGER NOT NULL, user_id INTEGER, update_type TEXT DEFAULT 'note', message TEXT NOT NULL, is_internal BOOLEAN DEFAULT 0, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY(ticket_id) REFERENCES support_tickets(id) )''') c.execute('''CREATE TABLE IF NOT EXISTS ticket_categories ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT UNIQUE NOT NULL, description TEXT, default_priority TEXT DEFAULT 'medium', is_active BOOLEAN DEFAULT 1 )''') conn.commit() c.execute("INSERT OR IGNORE INTO ticket_categories (name, description) VALUES ('General', 'General inquiries')") conn.commit() def hash_password(self, password: str): salt = secrets.token_hex(32) ph = hashlib.pbkdf2_hmac('sha256', password.encode(), salt.encode(), 100000).hex() return ph, salt def verify_password(self, password, phash, salt): return hashlib.pbkdf2_hmac('sha256', password.encode(), salt.encode(), 100000).hex() == phash def create_user(self, email, first_name, last_name, password, phone=None, company=None) -> Optional[int]: try: phash, salt = self.hash_password(password) with self.get_connection() as conn: cur = conn.cursor() cur.execute('''INSERT INTO users (email, first_name, last_name, password_hash, salt, phone, company) VALUES (?,?,?,?,?,?,?)''', (email, first_name, last_name, phash, salt, phone, company)) uid = cur.lastrowid conn.commit() return uid except sqlite3.IntegrityError: return None def authenticate_user(self, email, password) -> Optional[Dict]: with self.get_connection() as conn: cur = conn.cursor() cur.execute('SELECT id, email, first_name, last_name, password_hash, salt, is_active FROM users WHERE email = ?', (email,)) row = cur.fetchone() if row and row['is_active'] and self.verify_password(password, row['password_hash'], row['salt']): return dict(row) return None def create_session(self, user_id, ip, ua, hours=24) -> str: sid = secrets.token_urlsafe(32) with self.get_connection() as conn: conn.execute('INSERT INTO sessions (id, user_id, ip_address, user_agent, expires_at) VALUES (?,?,?,?, datetime("now", "+? hours"))', (sid, user_id, ip, ua[:255], hours)) conn.commit() return sid def get_user_by_session(self, session_id) -> Optional[Dict]: with self.get_connection() as conn: cur = conn.cursor() cur.execute('''SELECT u.id, u.email, u.first_name, u.last_name, u.role FROM users u JOIN sessions s ON u.id = s.user_id WHERE s.id = ? AND s.is_active = 1 AND s.expires_at > CURRENT_TIMESTAMP''', (session_id,)) row = cur.fetchone() return dict(row) if row else None def get_user_role(self, user_id) -> str: with self.get_connection() as conn: cur = conn.cursor() cur.execute('SELECT role FROM users WHERE id = ?', (user_id,)) row = cur.fetchone() return row['role'] if row else 'user' def create_conversation(self, user_id=None, session_id=None, title=None) -> str: cid = secrets.token_urlsafe(16) with self.get_connection() as conn: conn.execute('INSERT INTO conversations (id, user_id, session_id, title) VALUES (?,?,?,?)', (cid, user_id, session_id, title)) conn.commit() return cid def add_message(self, conversation_id, role, content, model_used=None, response_time_ms=None): with self.get_connection() as conn: cur = conn.cursor() cur.execute('''INSERT INTO messages (conversation_id, role, content, model_used, response_time_ms) VALUES (?,?,?,?,?)''', (conversation_id, role, content, model_used, response_time_ms)) conn.execute('UPDATE conversations SET updated_at = CURRENT_TIMESTAMP WHERE id = ?', (conversation_id,)) conn.commit() return cur.lastrowid def get_conversation_history(self, conversation_id, limit=50) -> List[Dict]: with self.get_connection() as conn: cur = conn.cursor() cur.execute('SELECT is_active FROM conversations WHERE id = ?', (conversation_id,)) if not cur.fetchone(): return [] cur.execute('''SELECT role, content, timestamp, model_used FROM messages WHERE conversation_id = ? ORDER BY timestamp LIMIT ?''', (conversation_id, limit)) return [dict(row) for row in cur.fetchall()] def create_support_ticket(self, user_id, subject, description, category, conversation_id=None, priority='medium') -> str: import random, string tn = 'TMC-' + ''.join(random.choices(string.digits, k=6)) with self.get_connection() as conn: cur = conn.cursor() cur.execute('''INSERT INTO support_tickets (ticket_number, user_id, conversation_id, subject, description, category, priority) VALUES (?,?,?,?,?,?,?)''', (tn, user_id, conversation_id, subject, description, category, priority)) conn.commit() return tn def get_ticket_by_number(self, ticket_number) -> Optional[Dict]: with self.get_connection() as conn: cur = conn.cursor() cur.execute('SELECT * FROM support_tickets WHERE ticket_number = ?', (ticket_number,)) row = cur.fetchone() return dict(row) if row else None def get_ticket_updates(self, ticket_id, include_internal=False) -> List[Dict]: with self.get_connection() as conn: cur = conn.cursor() if include_internal: cur.execute('SELECT * FROM ticket_updates WHERE ticket_id = ? ORDER BY created_at', (ticket_id,)) else: cur.execute('SELECT * FROM ticket_updates WHERE ticket_id = ? AND is_internal = 0 ORDER BY created_at', (ticket_id,)) return [dict(row) for row in cur.fetchall()] def add_ticket_update(self, ticket_id, user_id, message, update_type='note', is_internal=False) -> int: with self.get_connection() as conn: cur = conn.cursor() cur.execute('''INSERT INTO ticket_updates (ticket_id, user_id, update_type, message, is_internal) VALUES (?,?,?,?,?)''', (ticket_id, user_id, update_type, message, is_internal)) conn.execute('UPDATE support_tickets SET updated_at = CURRENT_TIMESTAMP WHERE id = ?', (ticket_id,)) conn.commit() return cur.lastrowid def get_user_tickets(self, user_id, limit=20) -> List[Dict]: with self.get_connection() as conn: cur = conn.cursor() cur.execute('SELECT * FROM support_tickets WHERE user_id = ? ORDER BY created_at DESC LIMIT ?', (user_id, limit)) return [dict(row) for row in cur.fetchall()] def get_tickets_by_status(self, status, limit=50) -> List[Dict]: with self.get_connection() as conn: cur = conn.cursor() if status: cur.execute('SELECT * FROM support_tickets WHERE status = ? ORDER BY created_at DESC LIMIT ?', (status, limit)) else: cur.execute('SELECT * FROM support_tickets ORDER BY created_at DESC LIMIT ?', (limit,)) return [dict(row) for row in cur.fetchall()] # Stubs for additional methods used in original app def categorize_ticket_content(self, text): return "General" def escalate_ticket(self, ticket_id, reason, user_id=None): with self.get_connection() as conn: conn.execute("UPDATE support_tickets SET priority = 'high' WHERE id = ?", (ticket_id,)) conn.commit() return True def check_escalation_needed(self, ticket_id): return {"needs_escalation": False}