Files
anchorhost-backend/ticket_service.py
T
wfalkowski c177d7981d
Build and publish Docker image / build-and-publish (push) Successful in 2m45s
Refactor backend and add registry build workflow
2026-10-20 16:44:49 +00:00

293 lines
11 KiB
Python

"""Support ticket persistence and message operations."""
from database_service import database_connection, dict_from_row
from runtime_config import (
logger,
)
from typing import Dict, List, Optional
import uuid
__all__ = ['add_ticket_attachment', 'add_ticket_message', 'create_ticket', 'get_all_tickets', 'get_ticket', 'get_ticket_attachments', 'get_ticket_stats', 'get_user_tickets', 'update_ticket_status']
def create_ticket(user_id: str, subject: str, description: str, priority: str = 'medium', category: str = 'general') -> Optional[Dict]:
"""Create a new support ticket"""
try:
ticket_id = f"TKT-{str(uuid.uuid4())[:8]}"
with database_connection() as conn:
cursor = conn.cursor()
cursor.execute('''
INSERT INTO tickets (id, user_id, subject, description, priority, category)
VALUES (?, ?, ?, ?, ?, ?)
''', (ticket_id, user_id, subject, description, priority, category))
conn.commit()
cursor.execute('''
SELECT t.*, u.username as user_username
FROM tickets t
JOIN users u ON t.user_id = u.id
WHERE t.id = ?
''', (ticket_id,))
ticket = dict_from_row(cursor.fetchone())
return ticket
except Exception as e:
logger.error(f"Ticket creation failed: {e}")
return None
def get_ticket(ticket_id: str) -> Optional[Dict]:
"""Get ticket by ID with user information"""
try:
with database_connection() as conn:
cursor = conn.cursor()
cursor.execute('''
SELECT t.*, u.username as user_username
FROM tickets t
JOIN users u ON t.user_id = u.id
WHERE t.id = ?
''', (ticket_id,))
ticket = dict_from_row(cursor.fetchone())
if ticket:
# Get ticket messages
cursor.execute('''
SELECT tm.*, u.username as user_username
FROM ticket_messages tm
JOIN users u ON tm.user_id = u.id
WHERE tm.ticket_id = ?
ORDER BY tm.created_at ASC
''', (ticket_id,))
messages = [dict_from_row(row) for row in cursor.fetchall()]
ticket['messages'] = messages
# Get message count
cursor.execute('SELECT COUNT(*) as count FROM ticket_messages WHERE ticket_id = ?', (ticket_id,))
ticket['message_count'] = cursor.fetchone()['count']
# Get last reply time
cursor.execute('''
SELECT MAX(created_at) as last_reply
FROM ticket_messages
WHERE ticket_id = ?
''', (ticket_id,))
last_reply = cursor.fetchone()['last_reply']
ticket['last_reply'] = last_reply
return ticket
except Exception as e:
logger.error(f"Error getting ticket: {e}")
return None
def get_user_tickets(user_id: str, include_closed: bool = False) -> List[Dict]:
"""Get all tickets for a user"""
try:
with database_connection() as conn:
cursor = conn.cursor()
if include_closed:
cursor.execute('''
SELECT t.*, u.username as user_username
FROM tickets t
JOIN users u ON t.user_id = u.id
WHERE t.user_id = ?
ORDER BY t.updated_at DESC
''', (user_id,))
else:
cursor.execute('''
SELECT t.*, u.username as user_username
FROM tickets t
JOIN users u ON t.user_id = u.id
WHERE t.user_id = ? AND t.status != 'closed'
ORDER BY t.updated_at DESC
''', (user_id,))
tickets = [dict_from_row(row) for row in cursor.fetchall()]
# Add message counts and last reply
for ticket in tickets:
cursor.execute('SELECT COUNT(*) as count FROM ticket_messages WHERE ticket_id = ?', (ticket['id'],))
ticket['message_count'] = cursor.fetchone()['count']
cursor.execute('''
SELECT MAX(created_at) as last_reply
FROM ticket_messages
WHERE ticket_id = ?
''', (ticket['id'],))
last_reply = cursor.fetchone()['last_reply']
ticket['last_reply'] = last_reply
return tickets
except Exception as e:
logger.error(f"Error getting user tickets: {e}")
return []
def get_all_tickets(status_filter: str = None, priority_filter: str = None) -> List[Dict]:
"""Get all tickets (admin only)"""
try:
with database_connection() as conn:
cursor = conn.cursor()
query = '''
SELECT t.*, u.username as user_username
FROM tickets t
JOIN users u ON t.user_id = u.id
'''
params = []
conditions = []
if status_filter and status_filter != 'all':
conditions.append('t.status = ?')
params.append(status_filter)
if priority_filter and priority_filter != 'all':
conditions.append('t.priority = ?')
params.append(priority_filter)
if conditions:
query += ' WHERE ' + ' AND '.join(conditions)
query += ' ORDER BY t.updated_at DESC'
cursor.execute(query, params)
tickets = [dict_from_row(row) for row in cursor.fetchall()]
# Add message counts and last reply
for ticket in tickets:
cursor.execute('SELECT COUNT(*) as count FROM ticket_messages WHERE ticket_id = ?', (ticket['id'],))
ticket['message_count'] = cursor.fetchone()['count']
cursor.execute('''
SELECT MAX(created_at) as last_reply
FROM ticket_messages
WHERE ticket_id = ?
''', (ticket['id'],))
last_reply = cursor.fetchone()['last_reply']
ticket['last_reply'] = last_reply
return tickets
except Exception as e:
logger.error(f"Error getting all tickets: {e}")
return []
def add_ticket_message(ticket_id: str, user_id: str, message: str, is_admin: bool = False) -> Optional[Dict]:
"""Add a message to a ticket"""
try:
message_id = f"MSG-{str(uuid.uuid4())[:8]}"
with database_connection() as conn:
cursor = conn.cursor()
# Add message
cursor.execute('''
INSERT INTO ticket_messages (id, ticket_id, user_id, message, is_admin)
VALUES (?, ?, ?, ?, ?)
''', (message_id, ticket_id, user_id, message, is_admin))
# Update ticket timestamp
cursor.execute('''
UPDATE tickets
SET updated_at = CURRENT_TIMESTAMP
WHERE id = ?
''', (ticket_id,))
conn.commit()
# Get the created message with user info
cursor.execute('''
SELECT tm.*, u.username as user_username
FROM ticket_messages tm
JOIN users u ON tm.user_id = u.id
WHERE tm.id = ?
''', (message_id,))
message_data = dict_from_row(cursor.fetchone())
return message_data
except Exception as e:
logger.error(f"Error adding ticket message: {e}")
return None
def update_ticket_status(ticket_id: str, status: str, assigned_admin_id: str = None) -> bool:
"""Update ticket status"""
try:
with database_connection() as conn:
cursor = conn.cursor()
if assigned_admin_id:
cursor.execute('''
UPDATE tickets
SET status = ?, assigned_admin_id = ?, updated_at = CURRENT_TIMESTAMP
WHERE id = ?
''', (status, assigned_admin_id, ticket_id))
else:
cursor.execute('''
UPDATE tickets
SET status = ?, updated_at = CURRENT_TIMESTAMP
WHERE id = ?
''', (status, ticket_id))
conn.commit()
return cursor.rowcount > 0
except Exception as e:
logger.error(f"Error updating ticket status: {e}")
return False
def add_ticket_attachment(ticket_id: str, message_id: str, filename: str, file_data: bytes,
mime_type: str, uploaded_by: str) -> Optional[Dict]:
"""Add attachment to ticket"""
try:
attachment_id = f"ATT-{str(uuid.uuid4())[:8]}"
file_size = len(file_data)
with database_connection() as conn:
cursor = conn.cursor()
cursor.execute('''
INSERT INTO ticket_attachments (id, ticket_id, message_id, filename, file_data, mime_type, file_size, uploaded_by)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
''', (attachment_id, ticket_id, message_id, filename, file_data, mime_type, file_size, uploaded_by))
conn.commit()
cursor.execute('SELECT * FROM ticket_attachments WHERE id = ?', (attachment_id,))
return dict_from_row(cursor.fetchone())
except Exception as e:
logger.error(f"Error adding ticket attachment: {e}")
return None
def get_ticket_attachments(ticket_id: str) -> List[Dict]:
"""Get all attachments for a ticket"""
try:
with database_connection() as conn:
cursor = conn.cursor()
cursor.execute('''
SELECT * FROM ticket_attachments
WHERE ticket_id = ?
ORDER BY created_at DESC
''', (ticket_id,))
return [dict_from_row(row) for row in cursor.fetchall()]
except Exception as e:
logger.error(f"Error getting ticket attachments: {e}")
return []
def get_ticket_stats() -> Dict:
"""Get ticket statistics"""
try:
with database_connection() as conn:
cursor = conn.cursor()
stats = {}
# Total tickets
cursor.execute('SELECT COUNT(*) as count FROM tickets')
stats['total'] = cursor.fetchone()['count']
# Tickets by status
cursor.execute('SELECT status, COUNT(*) as count FROM tickets GROUP BY status')
status_counts = cursor.fetchall()
for row in status_counts:
stats[row['status']] = row['count']
return stats
except Exception as e:
logger.error(f"Error getting ticket stats: {e}")
return {}