Apr 1 2026: clean up code
This commit is contained in:
+1
-1
@@ -170,7 +170,7 @@ def refresh():
|
|||||||
request.remote_addr)
|
request.remote_addr)
|
||||||
return api_error('Refresh token is invalid or expired', 401)
|
return api_error('Refresh token is invalid or expired', 401)
|
||||||
|
|
||||||
user = User.query.get(rt_row.user_id)
|
user = db.session.get(User, rt_row.user_id)
|
||||||
if user is None or not user.active:
|
if user is None or not user.active:
|
||||||
rt_row.revoke()
|
rt_row.revoke()
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ from flask import request, g, abort
|
|||||||
|
|
||||||
from app.api.jwt_utils import decode_access_token
|
from app.api.jwt_utils import decode_access_token
|
||||||
from app.api.errors import api_error
|
from app.api.errors import api_error
|
||||||
|
from app import db
|
||||||
from app.models.user import User
|
from app.models.user import User
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -62,7 +63,7 @@ def jwt_required(f):
|
|||||||
return api_error('Access token is invalid or expired', 401)
|
return api_error('Access token is invalid or expired', 401)
|
||||||
|
|
||||||
user_id = int(payload.get('sub', 0))
|
user_id = int(payload.get('sub', 0))
|
||||||
user = User.query.get(user_id)
|
user = db.session.get(User, user_id)
|
||||||
|
|
||||||
if user is None:
|
if user is None:
|
||||||
return api_error('User not found', 401)
|
return api_error('User not found', 401)
|
||||||
|
|||||||
+4
-7
@@ -2,6 +2,7 @@ import logging
|
|||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from flask import Blueprint, render_template, request, redirect, url_for, flash
|
from flask import Blueprint, render_template, request, redirect, url_for, flash
|
||||||
from flask_login import login_required
|
from flask_login import login_required
|
||||||
|
from app import db
|
||||||
from app.models.audit import AuditLog
|
from app.models.audit import AuditLog
|
||||||
from app.models.user import User
|
from app.models.user import User
|
||||||
from app.utils.decorators import admin_required
|
from app.utils.decorators import admin_required
|
||||||
@@ -55,13 +56,13 @@ def index():
|
|||||||
|
|
||||||
# Distinct action and entity_type values for the filter dropdowns
|
# Distinct action and entity_type values for the filter dropdowns
|
||||||
distinct_actions = (
|
distinct_actions = (
|
||||||
db.session.query(AuditLog.action)
|
AuditLog.query.with_entities(AuditLog.action)
|
||||||
.distinct()
|
.distinct()
|
||||||
.order_by(AuditLog.action)
|
.order_by(AuditLog.action)
|
||||||
.all()
|
.all()
|
||||||
)
|
)
|
||||||
distinct_entity_types = (
|
distinct_entity_types = (
|
||||||
db.session.query(AuditLog.entity_type)
|
AuditLog.query.with_entities(AuditLog.entity_type)
|
||||||
.distinct()
|
.distinct()
|
||||||
.order_by(AuditLog.entity_type)
|
.order_by(AuditLog.entity_type)
|
||||||
.all()
|
.all()
|
||||||
@@ -144,8 +145,4 @@ def purge():
|
|||||||
f'older than {label} have been permanently deleted.',
|
f'older than {label} have been permanently deleted.',
|
||||||
'success' if deleted else 'info',
|
'success' if deleted else 'info',
|
||||||
)
|
)
|
||||||
return redirect(url_for('audit.index'))
|
return redirect(url_for('audit.index'))
|
||||||
|
|
||||||
|
|
||||||
# Avoid circular import — imported after function definitions
|
|
||||||
from app import db # noqa: E402
|
|
||||||
@@ -249,8 +249,8 @@ def add_assignment(customer_id):
|
|||||||
def remove_assignment(assignment_id):
|
def remove_assignment(assignment_id):
|
||||||
assignment = CustomerAssignment.query.get_or_404(assignment_id)
|
assignment = CustomerAssignment.query.get_or_404(assignment_id)
|
||||||
customer_id = assignment.user_id
|
customer_id = assignment.user_id
|
||||||
customer = User.query.get(customer_id)
|
customer = db.session.get(User, customer_id)
|
||||||
project = Project.query.get(assignment.project_id)
|
project = db.session.get(Project, assignment.project_id)
|
||||||
|
|
||||||
username = customer.username if customer else f'user_id={customer_id}'
|
username = customer.username if customer else f'user_id={customer_id}'
|
||||||
project_name = project.name if project else f'project_id={assignment.project_id}'
|
project_name = project.name if project else f'project_id={assignment.project_id}'
|
||||||
|
|||||||
@@ -241,7 +241,7 @@ def facility_trend():
|
|||||||
if facility_id not in cids:
|
if facility_id not in cids:
|
||||||
return jsonify({'labels': [], 'data': [], 'facility': ''}), 403
|
return jsonify({'labels': [], 'data': [], 'facility': ''}), 403
|
||||||
|
|
||||||
facility = Facility.query.get(facility_id)
|
facility = db.session.get(Facility, facility_id)
|
||||||
if not facility:
|
if not facility:
|
||||||
return jsonify({'labels': [], 'data': [], 'facility': ''})
|
return jsonify({'labels': [], 'data': [], 'facility': ''})
|
||||||
|
|
||||||
|
|||||||
@@ -318,7 +318,7 @@ def execute(inspection_id):
|
|||||||
# fields (rating, pass_fail) so the inspector doesn't re-enter static
|
# fields (rating, pass_fail) so the inspector doesn't re-enter static
|
||||||
# data but must re-evaluate every scoreable item fresh.
|
# data but must re-evaluate every scoreable item fresh.
|
||||||
if not saved_responses and inspection.parent_inspection_id:
|
if not saved_responses and inspection.parent_inspection_id:
|
||||||
parent = Inspection.query.get(inspection.parent_inspection_id)
|
parent = db.session.get(Inspection, inspection.parent_inspection_id)
|
||||||
if parent and parent.notes:
|
if parent and parent.notes:
|
||||||
try:
|
try:
|
||||||
parent_parsed = json.loads(parent.notes)
|
parent_parsed = json.loads(parent.notes)
|
||||||
@@ -717,7 +717,7 @@ def flag_issue(inspection_id):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if issue.assigned_to:
|
if issue.assigned_to:
|
||||||
assignee = User.query.get(issue.assigned_to)
|
assignee = db.session.get(User, issue.assigned_to)
|
||||||
if assignee and assignee.id != current_user.id:
|
if assignee and assignee.id != current_user.id:
|
||||||
notify(
|
notify(
|
||||||
recipient = assignee,
|
recipient = assignee,
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ def view(issue_id):
|
|||||||
if current_user.role == 'customer':
|
if current_user.role == 'customer':
|
||||||
from app.models.facility import Area
|
from app.models.facility import Area
|
||||||
cids = get_customer_scope(current_user) or []
|
cids = get_customer_scope(current_user) or []
|
||||||
area = Area.query.get(issue.area_id)
|
area = db.session.get(Area, issue.area_id)
|
||||||
if not area or area.facility_id not in cids:
|
if not area or area.facility_id not in cids:
|
||||||
flash('Access denied.', 'danger')
|
flash('Access denied.', 'danger')
|
||||||
return redirect(url_for('issues.index'))
|
return redirect(url_for('issues.index'))
|
||||||
@@ -167,7 +167,7 @@ def view(issue_id):
|
|||||||
|
|
||||||
# 1. Status changed — notify assignee
|
# 1. Status changed — notify assignee
|
||||||
if old_status != issue.status and new_assigned_to:
|
if old_status != issue.status and new_assigned_to:
|
||||||
assignee = User.query.get(new_assigned_to)
|
assignee = db.session.get(User, new_assigned_to)
|
||||||
if assignee and assignee.id != actor_id:
|
if assignee and assignee.id != actor_id:
|
||||||
notify(
|
notify(
|
||||||
recipient = assignee,
|
recipient = assignee,
|
||||||
@@ -186,7 +186,7 @@ def view(issue_id):
|
|||||||
|
|
||||||
# 2. Reassigned — notify new assignee
|
# 2. Reassigned — notify new assignee
|
||||||
if (old_assigned_to != new_assigned_to) and new_assigned_to:
|
if (old_assigned_to != new_assigned_to) and new_assigned_to:
|
||||||
new_assignee = User.query.get(new_assigned_to)
|
new_assignee = db.session.get(User, new_assigned_to)
|
||||||
if new_assignee and new_assignee.id != actor_id:
|
if new_assignee and new_assignee.id != actor_id:
|
||||||
notify(
|
notify(
|
||||||
recipient = new_assignee,
|
recipient = new_assignee,
|
||||||
@@ -204,7 +204,7 @@ def view(issue_id):
|
|||||||
|
|
||||||
# 3. Unassigned — notify previous assignee
|
# 3. Unassigned — notify previous assignee
|
||||||
if old_assigned_to and old_assigned_to != new_assigned_to:
|
if old_assigned_to and old_assigned_to != new_assigned_to:
|
||||||
old_assignee = User.query.get(old_assigned_to)
|
old_assignee = db.session.get(User, old_assigned_to)
|
||||||
if old_assignee and old_assignee.id != actor_id:
|
if old_assignee and old_assignee.id != actor_id:
|
||||||
notify(
|
notify(
|
||||||
recipient = old_assignee,
|
recipient = old_assignee,
|
||||||
@@ -221,7 +221,7 @@ def view(issue_id):
|
|||||||
|
|
||||||
# 4. Comment added — notify assignee
|
# 4. Comment added — notify assignee
|
||||||
if comment_body and new_assigned_to:
|
if comment_body and new_assigned_to:
|
||||||
commentee = User.query.get(new_assigned_to)
|
commentee = db.session.get(User, new_assigned_to)
|
||||||
if commentee and commentee.id != actor_id:
|
if commentee and commentee.id != actor_id:
|
||||||
notify(
|
notify(
|
||||||
recipient = commentee,
|
recipient = commentee,
|
||||||
@@ -250,7 +250,8 @@ def view(issue_id):
|
|||||||
f'to "{issue.status.replace("_"," ").title()}"'
|
f'to "{issue.status.replace("_"," ").title()}"'
|
||||||
)
|
)
|
||||||
if old_assigned_to != new_assigned_to:
|
if old_assigned_to != new_assigned_to:
|
||||||
new_name = User.query.get(new_assigned_to).username if new_assigned_to else 'Unassigned'
|
_new_assignee_obj = db.session.get(User, new_assigned_to) if new_assigned_to else None
|
||||||
|
new_name = _new_assignee_obj.username if _new_assignee_obj else 'Unassigned'
|
||||||
changes.append(f'reassigned to {new_name}')
|
changes.append(f'reassigned to {new_name}')
|
||||||
if comment_body:
|
if comment_body:
|
||||||
changes.append(f'new comment added by {current_user.username}')
|
changes.append(f'new comment added by {current_user.username}')
|
||||||
@@ -378,7 +379,7 @@ def create():
|
|||||||
f'severity={issue.severity}; assigned_to={issue.assigned_to}')
|
f'severity={issue.severity}; assigned_to={issue.assigned_to}')
|
||||||
|
|
||||||
if issue.assigned_to:
|
if issue.assigned_to:
|
||||||
assignee = User.query.get(issue.assigned_to)
|
assignee = db.session.get(User, issue.assigned_to)
|
||||||
if assignee and assignee.id != current_user.id:
|
if assignee and assignee.id != current_user.id:
|
||||||
notify(
|
notify(
|
||||||
recipient = assignee,
|
recipient = assignee,
|
||||||
@@ -397,7 +398,7 @@ def create():
|
|||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
# ── Notify customer portal users for this facility ──────────
|
# ── Notify customer portal users for this facility ──────────
|
||||||
area = Area.query.get(issue.area_id)
|
area = db.session.get(Area, issue.area_id)
|
||||||
if area:
|
if area:
|
||||||
notify_customers_for_facility(
|
notify_customers_for_facility(
|
||||||
facility_id = area.facility_id,
|
facility_id = area.facility_id,
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import logging
|
|||||||
from flask import (Blueprint, jsonify, request, abort,
|
from flask import (Blueprint, jsonify, request, abort,
|
||||||
render_template, redirect, url_for, flash, current_app)
|
render_template, redirect, url_for, flash, current_app)
|
||||||
from flask_login import login_required, current_user
|
from flask_login import login_required, current_user
|
||||||
from app import db
|
from app import db, csrf
|
||||||
from app.models.notification import (
|
from app.models.notification import (
|
||||||
Notification, NotificationPreference, ALL_EVENT_TYPES
|
Notification, NotificationPreference, ALL_EVENT_TYPES
|
||||||
)
|
)
|
||||||
@@ -166,6 +166,7 @@ def preferences():
|
|||||||
# ── Digest trigger (called by cron) ───────────────────────────────────────────
|
# ── Digest trigger (called by cron) ───────────────────────────────────────────
|
||||||
|
|
||||||
@bp.route('/send-digest', methods=['POST'])
|
@bp.route('/send-digest', methods=['POST'])
|
||||||
|
@csrf.exempt
|
||||||
def send_digest():
|
def send_digest():
|
||||||
"""Trigger digest email delivery. Protected by a shared secret token.
|
"""Trigger digest email delivery. Protected by a shared secret token.
|
||||||
|
|
||||||
@@ -198,6 +199,7 @@ def send_digest():
|
|||||||
# ── SLA alert trigger (called by cron) ────────────────────────────────────────
|
# ── SLA alert trigger (called by cron) ────────────────────────────────────────
|
||||||
|
|
||||||
@bp.route('/check-sla', methods=['POST'])
|
@bp.route('/check-sla', methods=['POST'])
|
||||||
|
@csrf.exempt
|
||||||
def check_sla():
|
def check_sla():
|
||||||
"""Scan all open issues for SLA breaches and dispatch alerts.
|
"""Scan all open issues for SLA breaches and dispatch alerts.
|
||||||
|
|
||||||
@@ -219,4 +221,4 @@ def check_sla():
|
|||||||
sent = send_sla_alerts()
|
sent = send_sla_alerts()
|
||||||
|
|
||||||
logger.info('SLA CHECK TRIGGERED | notifications_sent=%s', sent)
|
logger.info('SLA CHECK TRIGGERED | notifications_sent=%s', sent)
|
||||||
return jsonify({'ok': True, 'notifications_sent': sent})
|
return jsonify({'ok': True, 'notifications_sent': sent})
|
||||||
@@ -181,7 +181,7 @@ def add_assignment(project_id):
|
|||||||
db.session.add(assignment)
|
db.session.add(assignment)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
user = User.query.get(form.user_id.data)
|
user = db.session.get(User, form.user_id.data)
|
||||||
scope_label = f'facility_id={facility_id}' if facility_id else 'all facilities'
|
scope_label = f'facility_id={facility_id}' if facility_id else 'all facilities'
|
||||||
logger.info('PROJECTS | assignment_add | admin=%s customer=%s project_id=%s scope=%s',
|
logger.info('PROJECTS | assignment_add | admin=%s customer=%s project_id=%s scope=%s',
|
||||||
current_user.username, user.username, project_id, scope_label)
|
current_user.username, user.username, project_id, scope_label)
|
||||||
@@ -208,7 +208,7 @@ def remove_assignment(assignment_id):
|
|||||||
assignment = CustomerAssignment.query.get_or_404(assignment_id)
|
assignment = CustomerAssignment.query.get_or_404(assignment_id)
|
||||||
project_id = assignment.project_id
|
project_id = assignment.project_id
|
||||||
project = Project.query.get_or_404(project_id)
|
project = Project.query.get_or_404(project_id)
|
||||||
user = User.query.get(assignment.user_id)
|
user = db.session.get(User, assignment.user_id)
|
||||||
|
|
||||||
username = user.username if user else f'user_id={assignment.user_id}'
|
username = user.username if user else f'user_id={assignment.user_id}'
|
||||||
assignment_id_snap = assignment.id
|
assignment_id_snap = assignment.id
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ from flask_login import login_required, current_user
|
|||||||
from flask_mail import Message
|
from flask_mail import Message
|
||||||
from sqlalchemy import func
|
from sqlalchemy import func
|
||||||
|
|
||||||
from app import db, mail
|
from app import db, mail, csrf
|
||||||
from app.models.scheduled_report import ScheduledReport
|
from app.models.scheduled_report import ScheduledReport
|
||||||
from app.models.inspection import Inspection, InspectionTemplate
|
from app.models.inspection import Inspection, InspectionTemplate
|
||||||
from app.models.facility import Facility, Area
|
from app.models.facility import Facility, Area
|
||||||
@@ -419,6 +419,9 @@ def send_now(report_id):
|
|||||||
if ok:
|
if ok:
|
||||||
report.last_sent_at = now_eastern()
|
report.last_sent_at = now_eastern()
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
log_action(ACTION_UPDATE, 'ScheduledReport', report.id, report.name,
|
||||||
|
f'manual send_now by {current_user.username}; '
|
||||||
|
f'frequency={report.frequency}; recipients={len(report.recipient_list())}')
|
||||||
flash(f'Report "{report.name}" sent successfully.', 'success')
|
flash(f'Report "{report.name}" sent successfully.', 'success')
|
||||||
else:
|
else:
|
||||||
flash(f'Failed to send report "{report.name}". Check application logs.', 'danger')
|
flash(f'Failed to send report "{report.name}". Check application logs.', 'danger')
|
||||||
@@ -428,6 +431,7 @@ def send_now(report_id):
|
|||||||
# ── Cron endpoint ─────────────────────────────────────────────────────────────
|
# ── Cron endpoint ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
@bp.route('/send', methods=['POST'])
|
@bp.route('/send', methods=['POST'])
|
||||||
|
@csrf.exempt
|
||||||
def send():
|
def send():
|
||||||
"""Token-protected endpoint called by cron to dispatch due reports.
|
"""Token-protected endpoint called by cron to dispatch due reports.
|
||||||
|
|
||||||
@@ -452,12 +456,20 @@ def send():
|
|||||||
sent, failed = 0, 0
|
sent, failed = 0, 0
|
||||||
for report in due:
|
for report in due:
|
||||||
ok = _send_report(report)
|
ok = _send_report(report)
|
||||||
|
# Always advance next_send_at so a failed report does not get
|
||||||
|
# retried on every subsequent cron run. last_sent_at is only
|
||||||
|
# updated on a successful delivery so the UI accurately reflects
|
||||||
|
# when the last good email was dispatched.
|
||||||
|
report.next_send_at = _compute_next_send(frequency, now)
|
||||||
if ok:
|
if ok:
|
||||||
report.last_sent_at = now
|
report.last_sent_at = now
|
||||||
report.next_send_at = _compute_next_send(frequency, now)
|
|
||||||
sent += 1
|
sent += 1
|
||||||
else:
|
else:
|
||||||
failed += 1
|
failed += 1
|
||||||
|
logger.error(
|
||||||
|
'SCHEDULED REPORT FAILED | id=%s | name=%r | frequency=%s',
|
||||||
|
report.id, report.name, frequency,
|
||||||
|
)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
logger.info('SCHEDULED REPORTS CRON | frequency=%s | due=%s | sent=%s | failed=%s',
|
logger.info('SCHEDULED REPORTS CRON | frequency=%s | due=%s | sent=%s | failed=%s',
|
||||||
|
|||||||
+11
-1
@@ -305,6 +305,9 @@ def create_checklist_item(template_id):
|
|||||||
)
|
)
|
||||||
db.session.add(item)
|
db.session.add(item)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
log_action(ACTION_CREATE, 'ChecklistItem', item.id, item.item_description[:80],
|
||||||
|
f'template_id={template.id}; category={item.category or ""}; '
|
||||||
|
f'scoring_type={item.scoring_type}')
|
||||||
|
|
||||||
flash('Checklist item added successfully.', 'success')
|
flash('Checklist item added successfully.', 'success')
|
||||||
return redirect(url_for('templates.edit_template', template_id=template.id))
|
return redirect(url_for('templates.edit_template', template_id=template.id))
|
||||||
@@ -331,6 +334,9 @@ def edit_checklist_item(item_id):
|
|||||||
item.weight = form.weight.data
|
item.weight = form.weight.data
|
||||||
item.requires_photo = form.requires_photo.data
|
item.requires_photo = form.requires_photo.data
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
log_action(ACTION_UPDATE, 'ChecklistItem', item.id, item.item_description[:80],
|
||||||
|
f'template_id={item.template_id}; category={item.category or ""}; '
|
||||||
|
f'scoring_type={item.scoring_type}')
|
||||||
flash('Checklist item updated successfully.', 'success')
|
flash('Checklist item updated successfully.', 'success')
|
||||||
return redirect(url_for('templates.edit_template', template_id=item.template_id))
|
return redirect(url_for('templates.edit_template', template_id=item.template_id))
|
||||||
|
|
||||||
@@ -349,8 +355,12 @@ def edit_checklist_item(item_id):
|
|||||||
def delete_checklist_item(item_id):
|
def delete_checklist_item(item_id):
|
||||||
item = ChecklistItem.query.get_or_404(item_id)
|
item = ChecklistItem.query.get_or_404(item_id)
|
||||||
template_id = item.template_id
|
template_id = item.template_id
|
||||||
|
item_desc = item.item_description[:80]
|
||||||
|
item_id_snap = item.id
|
||||||
db.session.delete(item)
|
db.session.delete(item)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
log_action(ACTION_DELETE, 'ChecklistItem', item_id_snap, item_desc,
|
||||||
|
f'template_id={template_id}')
|
||||||
flash('Checklist item deleted successfully.', 'success')
|
flash('Checklist item deleted successfully.', 'success')
|
||||||
return redirect(url_for('templates.edit_template', template_id=template_id))
|
return redirect(url_for('templates.edit_template', template_id=template_id))
|
||||||
|
|
||||||
@@ -363,7 +373,7 @@ def reorder_items(template_id):
|
|||||||
item_order = request.json.get('item_order', [])
|
item_order = request.json.get('item_order', [])
|
||||||
|
|
||||||
for index, item_id in enumerate(item_order):
|
for index, item_id in enumerate(item_order):
|
||||||
item = ChecklistItem.query.get(item_id)
|
item = db.session.get(ChecklistItem, item_id)
|
||||||
if item and item.template_id == template.id:
|
if item and item.template_id == template.id:
|
||||||
item.display_order = index
|
item.display_order = index
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user