05/24 Fix bugs

This commit is contained in:
2026-05-24 22:36:36 -04:00
parent 19bad27866
commit 9e1493eea7
5 changed files with 122 additions and 9 deletions
+28
View File
@@ -1668,3 +1668,31 @@ def consume_password_reset_token(token: str, new_password: str) -> bool:
finally:
if conn:
conn.close()
def purge_expired_reset_tokens():
"""Delete all expired password reset tokens."""
conn = None
try:
conn = get_connection()
cur = conn.cursor()
cur.execute("DELETE FROM password_reset_tokens WHERE expires_at < NOW()")
conn.commit()
cur.close()
finally:
if conn:
conn.close()
def delete_ai_analysis(analysis_id: int):
"""Permanently delete an AI analysis record."""
conn = None
try:
conn = get_connection()
cur = conn.cursor()
cur.execute("DELETE FROM ai_analysis_log WHERE id=%s", (analysis_id,))
conn.commit()
cur.close()
finally:
if conn:
conn.close()
+24 -8
View File
@@ -13,7 +13,7 @@ from flask import (Blueprint, render_template, request, redirect, url_for,
from models import (
get_all_criteria, get_active_criteria, create_criterion, update_criterion,
delete_criterion, save_ai_analysis, get_ai_analysis_history,
get_ai_analysis_detail, log_action,
get_ai_analysis_detail, delete_ai_analysis, log_action,
)
from config import get_setting
from utils.decorators import login_required, admin_required
@@ -370,11 +370,27 @@ def analysis_detail(analysis_id):
if user["role"] != "admin" and record["user_id"] != user["id"]:
return jsonify({"error": "Access denied"}), 403
return jsonify({
"id": record["id"],
"username": record.get("username"),
"file_names": record["file_names"],
"model": record["model"],
"verdict": record["verdict"],
"analyzed_at": str(record["analyzed_at"]),
"summary_text": record["summary_text"],
"id": record["id"],
"username": record.get("username"),
"file_names": record["file_names"],
"model": record["model"],
"verdict": record["verdict"],
"analyzed_at": str(record["analyzed_at"]),
"summary_text": record["summary_text"],
"criteria_snapshot": record.get("criteria_snapshot") or "",
})
@ai_summary_bp.route("/history/<int:analysis_id>/delete", methods=["POST"])
@login_required
def delete_analysis(analysis_id):
record = get_ai_analysis_detail(analysis_id)
if not record:
return jsonify({"error": "Not found"}), 404
user = session["user"]
if user["role"] != "admin" and record["user_id"] != user["id"]:
return jsonify({"error": "Access denied"}), 403
delete_ai_analysis(analysis_id)
log_action(user["id"], "DELETE_AI_ANALYSIS", "ai_analysis_log", analysis_id,
f"Deleted AI analysis id={analysis_id}.")
return jsonify({"ok": True})
+5
View File
@@ -8,6 +8,7 @@ from models import (
authenticate, check_login_allowed, change_password, log_action,
get_user_by_email, create_password_reset_token,
get_password_reset_user, consume_password_reset_token,
purge_expired_reset_tokens,
)
from utils.decorators import login_required
from utils.email import send_email
@@ -46,6 +47,10 @@ def login():
"role": user["role"],
"email": user.get("email", ""),
}
try:
purge_expired_reset_tokens()
except Exception:
pass # non-critical cleanup; never fail a login because of it
logger.info(f"User '{username}' logged in from {ip}.")
flash(f"You have been signed in. Welcome, {session['user']['full_name']}!", "success")
if user["role"] == "admin":
+24
View File
@@ -220,5 +220,29 @@ function buildShiftForm(s) {
<textarea class="form-control" name="note" rows="2">${esc(s.note)}</textarea></div>`;
}
function esc(s) { return (s||'').replace(/&/g,'&amp;').replace(/"/g,'&quot;').replace(/</g,'&lt;'); }
// Highlight today's column in the calendar
(function() {
var jsDay = new Date().getDay(); // 0=Sun … 6=Sat
var mysqlDay = String(jsDay === 0 ? 1 : jsDay + 1); // MySQL DAYOFWEEK: 1=Sun, 2=Mon…
var colIdx = DAY_MAP.findIndex(function(d) { return d[1] === mysqlDay; });
if (colIdx === -1) return;
var calTable = document.querySelector('#tab-calendar table');
if (!calTable) return;
calTable.querySelectorAll('tr').forEach(function(tr) {
var cells = tr.querySelectorAll('th, td');
var cell = cells[colIdx + 1]; // +1 for the leading Shift name column
if (cell) cell.classList.add('cal-today');
});
})();
</script>
<style>
.cal-today { background: #eff6ff !important; }
.cal-today th, thead .cal-today {
background: #dbeafe !important;
font-weight: 700;
color: #1d4ed8;
}
</style>
{% endblock %}
+41 -1
View File
@@ -233,9 +233,11 @@
<div id="hist-verdict-banner" style="display:none"></div>
<div class="modal-body" style="padding:0">
<div class="ai-meta-bar" id="hist-meta"></div>
<div id="hist-body" class="ai-rendered-output" style="max-height:65vh;overflow-y:auto;padding:1.25rem 1.5rem"></div>
<div id="hist-criteria" style="display:none;padding:.6rem 1.5rem;font-size:.78rem;background:var(--bg-subtle);border-bottom:1px solid var(--border)"></div>
<div id="hist-body" class="ai-rendered-output" style="max-height:60vh;overflow-y:auto;padding:1.25rem 1.5rem"></div>
</div>
<div class="modal-footer">
<button class="btn btn-danger btn-sm" id="btn-delete-analysis" style="margin-right:auto;display:none" onclick="deleteAnalysis()">🗑 Delete</button>
<button class="btn btn-secondary" onclick="closeModal('modal-history')">Close</button>
</div>
</div>
@@ -407,13 +409,18 @@ document.querySelectorAll('.js-edit-criterion').forEach(function(btn) {
});
/* ─── History rows ──────────────────────────────────────────── */
var _currentAnalysisId = null;
document.querySelectorAll('.history-row').forEach(function(row) {
row.addEventListener('click', function() {
var id = row.dataset.id;
_currentAnalysisId = id;
document.getElementById('hist-title').textContent = 'Loading…';
document.getElementById('hist-meta').textContent = '';
document.getElementById('hist-body').innerHTML = '';
document.getElementById('hist-criteria').style.display = 'none';
document.getElementById('hist-verdict-banner').style.display = 'none';
document.getElementById('btn-delete-analysis').style.display = 'none';
openModal('modal-history');
fetch('/ai-summary/history/' + id)
@@ -435,8 +442,23 @@ document.querySelectorAll('.history-row').forEach(function(row) {
vb.innerHTML = verdictBannerHTML(d.verdict);
vb.style.display = '';
}
// Criteria snapshot
var criteriaEl = document.getElementById('hist-criteria');
if (d.criteria_snapshot) {
try {
var snap = JSON.parse(d.criteria_snapshot);
if (snap && snap.length) {
var labels = snap.map(function(c) { return c.title; }).join(' · ');
criteriaEl.innerHTML = '<strong style="color:var(--text)">Criteria used:</strong> ' + escHtml(labels);
criteriaEl.style.display = '';
}
} catch(e) { /* ignore malformed snapshot */ }
}
document.getElementById('hist-body').innerHTML =
renderAI(d.summary_text || '');
document.getElementById('btn-delete-analysis').style.display = '';
})
.catch(function() {
document.getElementById('hist-body').textContent = 'Failed to load analysis.';
@@ -444,6 +466,24 @@ document.querySelectorAll('.history-row').forEach(function(row) {
});
});
function deleteAnalysis() {
if (!_currentAnalysisId) return;
if (!confirm('Delete this analysis? This cannot be undone.')) return;
fetch('/ai-summary/history/' + _currentAnalysisId + '/delete', {
method: 'POST',
headers: { 'X-CSRFToken': getCsrfToken() },
}).then(function(r) { return r.json(); })
.then(function(d) {
if (d.error) { alert(d.error); return; }
closeModal('modal-history');
// Remove the row from the table
var row = document.querySelector('.history-row[data-id="' + _currentAnalysisId + '"]');
if (row) row.remove();
_currentAnalysisId = null;
})
.catch(function() { alert('Delete failed. Please try again.'); });
}
/* ─── Utility ───────────────────────────────────────────────── */
function escHtml(str) {
return String(str || '').replace(/[&<>"']/g, function(c) {