import logging from flask import request from app import db from app.models import ActivityLog logger = logging.getLogger(__name__) def _get_real_ip(): """ Return the genuine client IP address when Flask sits behind an nginx reverse proxy. nginx forwards the original client IP in two headers: - X-Forwarded-For: , , , ... - X-Real-IP: request.remote_addr is always 127.0.0.1 in a proxied setup, so we must read the headers instead. We take the leftmost (first) value in X-Forwarded-For because that is the original client; subsequent values are intermediate proxies appended by each hop. Falls back to X-Real-IP, then remote_addr as a last resort. """ try: xff = request.headers.get('X-Forwarded-For') if xff: # Strip any port appended by some proxies and take first entry return xff.split(',')[0].strip() xri = request.headers.get('X-Real-IP') if xri: return xri.strip() return request.remote_addr except RuntimeError: return None def log_action(user_id, action, entity_type=None, entity_id=None, details=None): """ Persist an activity log entry for create / edit / delete actions. Parameters ---------- user_id : int | None – the acting user (None for system actions) action : str – e.g. 'ticket_create', 'ticket_update', 'comment_delete' entity_type : str | None – 'ticket', 'comment', 'user', etc. entity_id : int | None – primary key of the affected entity details : str | None – free-form JSON or human-readable details """ ip = _get_real_ip() try: entry = ActivityLog( user_id = user_id, action = action, entity_type = entity_type, entity_id = entity_id, details = details, ip_address = ip, ) db.session.add(entry) db.session.commit() logger.info( f'[ACTIVITY] action={action} entity={entity_type}:{entity_id} ' f'user_id={user_id} ip={ip}' ) except Exception as exc: db.session.rollback() logger.error(f'[ACTIVITY LOG ERROR] {exc}') def log_ticket_history(ticket, field_name, old_value, new_value, changed_by_id): """Record a granular field-level change on a ticket.""" from app.models import TicketHistory try: entry = TicketHistory( ticket_id = ticket.id, changed_by = changed_by_id, field_name = field_name, old_value = str(old_value) if old_value is not None else None, new_value = str(new_value) if new_value is not None else None, ) db.session.add(entry) db.session.commit() logger.info( f'[TICKET HISTORY] ticket_id={ticket.id} field={field_name} ' f'"{old_value}" -> "{new_value}" by user_id={changed_by_id}' ) except Exception as exc: db.session.rollback() logger.error(f'[TICKET HISTORY ERROR] {exc}')