Initial commit
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
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: <client>, <proxy1>, <proxy2>, ...
|
||||
- X-Real-IP: <client>
|
||||
|
||||
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}')
|
||||
Reference in New Issue
Block a user