05/24 Enhance functionalities

This commit is contained in:
2026-05-24 18:10:11 -04:00
parent 6605484927
commit fbf271ed71
15 changed files with 593 additions and 30 deletions
+188
View File
@@ -1480,3 +1480,191 @@ def purge_app_log(older_than_days: int = 30):
finally:
if conn:
conn.close()
# ─── Missed Shifts ────────────────────────────────────────────────────────────
def get_missed_shifts_today() -> list:
"""Return shift+user pairs scheduled today where the user has checked 0 sites."""
conn = None
try:
conn = get_connection()
cur = conn.cursor(dictionary=True)
cur.execute(
"""
SELECT s.name AS shift_name,
u.id AS user_id, u.username,
COALESCE(u.full_name, u.username) AS full_name,
COUNT(DISTINCT sw.website_id) AS total_sites,
COUNT(DISTINCT sc.website_id) AS checked_sites
FROM shifts s
JOIN shift_users su ON su.shift_id = s.id
JOIN users u ON u.id = su.user_id AND u.is_active = 1
JOIN shift_websites sw ON sw.shift_id = s.id
LEFT JOIN shift_checks sc
ON sc.user_id = su.user_id
AND sc.website_id = sw.website_id
AND DATE(sc.checked_at) = CURDATE()
WHERE s.is_active = 1
AND LOCATE(CAST(DAYOFWEEK(CURDATE()) AS CHAR), s.days_of_week) > 0
GROUP BY s.id, u.id
HAVING total_sites > 0 AND checked_sites = 0
ORDER BY s.name, u.username
"""
)
rows = cur.fetchall()
cur.close()
return rows
finally:
if conn:
conn.close()
# ─── Bid Reminders ────────────────────────────────────────────────────────────
def get_bids_due_soon(days: int = 7) -> list:
"""Return open/monitoring bids with due_date within the next `days` days."""
conn = None
try:
conn = get_connection()
cur = conn.cursor(dictionary=True)
cur.execute(
"SELECT b.*, u.username AS added_by_username, u.full_name AS added_by_full_name"
" FROM bid_tracker b"
" LEFT JOIN users u ON u.id = b.added_by"
" WHERE b.status IN ('open','monitoring')"
" AND b.due_date IS NOT NULL"
" AND b.due_date BETWEEN CURDATE() AND DATE_ADD(CURDATE(), INTERVAL %s DAY)"
" ORDER BY b.due_date ASC",
(days,),
)
rows = cur.fetchall()
cur.close()
return rows
finally:
if conn:
conn.close()
def get_admin_emails() -> list:
"""Return email addresses of all active admin users that have one set."""
conn = None
try:
conn = get_connection()
cur = conn.cursor(dictionary=True)
cur.execute(
"SELECT email FROM users"
" WHERE role='admin' AND is_active=1 AND email IS NOT NULL AND email != ''"
)
rows = cur.fetchall()
cur.close()
return [r["email"] for r in rows]
finally:
if conn:
conn.close()
# ─── Server-side Health Check ─────────────────────────────────────────────────
def get_website_url(website_id: int):
"""Return the URL of an active website by ID, or None if not found."""
conn = None
try:
conn = get_connection()
cur = conn.cursor()
cur.execute("SELECT url FROM websites WHERE id=%s AND is_active=1", (website_id,))
row = cur.fetchone()
cur.close()
return row[0] if row else None
finally:
if conn:
conn.close()
# ─── Password Reset ───────────────────────────────────────────────────────────
def get_user_by_email(email: str):
"""Return the active user matching the given email (case-insensitive), or None."""
conn = None
try:
conn = get_connection()
cur = conn.cursor(dictionary=True)
cur.execute(
"SELECT * FROM users WHERE LOWER(email)=%s AND is_active=1 LIMIT 1",
(email.lower(),),
)
row = cur.fetchone()
cur.close()
return row
finally:
if conn:
conn.close()
def create_password_reset_token(user_id: int) -> str:
"""Generate a secure 1-hour reset token; invalidates any prior token for the user."""
import secrets
token = secrets.token_urlsafe(48)
conn = None
try:
conn = get_connection()
cur = conn.cursor()
cur.execute("DELETE FROM password_reset_tokens WHERE user_id=%s", (user_id,))
cur.execute(
"INSERT INTO password_reset_tokens (user_id, token, expires_at)"
" VALUES (%s, %s, DATE_ADD(NOW(), INTERVAL 1 HOUR))",
(user_id, token),
)
conn.commit()
cur.close()
return token
finally:
if conn:
conn.close()
def get_password_reset_user(token: str):
"""Return the user row for a valid, unexpired reset token, or None."""
conn = None
try:
conn = get_connection()
cur = conn.cursor(dictionary=True)
cur.execute(
"SELECT u.* FROM password_reset_tokens t"
" JOIN users u ON u.id = t.user_id"
" WHERE t.token=%s AND t.expires_at > NOW()",
(token,),
)
row = cur.fetchone()
cur.close()
return row
finally:
if conn:
conn.close()
def consume_password_reset_token(token: str, new_password: str) -> bool:
"""Validate token, update the user's password, and delete the token. Returns True on success."""
conn = None
try:
conn = get_connection()
cur = conn.cursor(dictionary=True)
cur.execute(
"SELECT user_id FROM password_reset_tokens WHERE token=%s AND expires_at > NOW()",
(token,),
)
row = cur.fetchone()
if not row:
cur.close()
return False
user_id = row["user_id"]
new_hash = _hash_password(new_password)
cur = conn.cursor()
cur.execute("UPDATE users SET password=%s WHERE id=%s", (new_hash, user_id))
cur.execute("DELETE FROM password_reset_tokens WHERE token=%s", (token,))
conn.commit()
cur.close()
return True
finally:
if conn:
conn.close()