Initial commit

This commit is contained in:
2026-03-25 11:37:46 -04:00
commit dfdbb54875
41 changed files with 5079 additions and 0 deletions
+91
View File
@@ -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}')