04/08 Refactored codes, fixed issues

This commit is contained in:
2026-04-08 16:12:33 -04:00
parent c5e6566b6d
commit 6e1c9bbc7d
11 changed files with 84 additions and 124 deletions
+1
View File
@@ -208,3 +208,4 @@ marimo/_static/
marimo/_lsp/
__marimo__/
README.md
Claude.md
+52 -64
View File
@@ -175,7 +175,10 @@ def create_app() -> Flask:
@app.before_request
def log_request_info():
"""Log request information for security monitoring"""
"""Record request start time and scan for suspicious user agents"""
# Always record start time for slow-query detection in after_request
g.start_time = _time.time()
if (request.endpoint and
(request.endpoint.startswith('static') or
request.path.startswith('/api/logs'))):
@@ -198,14 +201,24 @@ def create_app() -> Flask:
@app.after_request
def log_response_info(response):
"""Log response information for performance monitoring"""
"""Log slow requests and error responses for performance and health monitoring"""
from extensions import logger_handler as lh
if request.endpoint and request.endpoint.startswith('static'):
return response
if hasattr(request, 'start_time'):
duration = _time.time() - request.start_time
if duration > 5.0:
lh.logger.warning(f"Slow request: {request.path} took {duration:.2f} seconds")
if hasattr(g, 'start_time'):
duration = _time.time() - g.start_time
if duration > 2.0:
lh.log_system_event(
event_type="slow_query_detected",
description=f"Slow request: {request.endpoint} took {duration:.2f}s",
severity="WARNING",
additional_data={
'duration': duration,
'endpoint': request.endpoint,
'method': request.method,
'user': session.get('username', 'anonymous')
}
)
if response.status_code >= 400:
lh.logger.warning(
f"Error response: {response.status_code} for {request.path} "
@@ -217,37 +230,22 @@ def create_app() -> Flask:
# Error handlers
# ------------------------------------------------------------------
@app.errorhandler(403)
def forbidden(error):
"""Handle forbidden access errors"""
return render_template('errors/403.html'), 403
@app.errorhandler(404)
def not_found(error):
"""Handle page not found errors"""
return render_template('errors/404.html'), 404
@app.errorhandler(500)
def internal_error(error):
"""Handle internal server errors with user-friendly page"""
if app.debug:
return None
return '''
<!DOCTYPE html>
<html>
<head><title>Server Error</title></head>
<body style="font-family: Arial; text-align: center; margin-top: 100px;">
<h1>🔧 Something went wrong</h1>
<p>We're working to fix this issue. Please try again later.</p>
<a href="/" style="color: #2563eb;">← Back to Home</a>
</body>
</html>
''', 500
@app.errorhandler(404)
def not_found(error):
"""Handle page not found errors"""
return '''
<!DOCTYPE html>
<html>
<head><title>Page Not Found</title></head>
<body style="font-family: Arial; text-align: center; margin-top: 100px;">
<h1>🔍 Page Not Found</h1>
<p>The page you're looking for doesn't exist.</p>
<a href="/" style="color: #2563eb;">← Back to Home</a>
</body>
</html>
''', 404
return render_template('errors/500.html'), 500
return app
@@ -293,13 +291,30 @@ def create_tables():
def update_existing_qr_codes():
"""Update existing QR codes with URLs and regenerate QR images with logging"""
"""Update existing QR codes with missing URLs or images at startup.
Regenerates qr_url slugs without needing a request context.
For qr_code_image, constructs the base URL from FLASK_HOST/FLASK_PORT
config so this can run safely outside any HTTP request.
"""
from extensions import db as _db, logger_handler as lh
from utils.helpers import generate_qr_code, get_qr_styling, generate_qr_url
from flask import current_app, request
from config import Config as _Cfg
try:
from models.qrcode import QRCode
qr_codes = QRCode.query.filter_by(active_status=True).all()
if not qr_codes:
return
# Build a base URL that does not require an active request context.
host = os.environ.get('FLASK_HOST', '0.0.0.0')
# 0.0.0.0 is a bind address, not a reachable hostname — default to localhost
if host in ('0.0.0.0', ''):
host = 'localhost'
port = os.environ.get('FLASK_PORT', '5000')
scheme = 'https' if _Cfg.SESSION_COOKIE_SECURE else 'http'
base_url = f"{scheme}://{host}:{port}/"
updated_count = 0
for qr_code in qr_codes:
if not qr_code.qr_url or not qr_code.qr_code_image:
@@ -307,7 +322,7 @@ def update_existing_qr_codes():
if not qr_code.qr_url:
qr_code.qr_url = generate_qr_url(qr_code.name, qr_code.id)
if not qr_code.qr_code_image:
qr_data = f"{request.url_root}qr/{qr_code.qr_url}"
qr_data = f"{base_url}qr/{qr_code.qr_url}"
styling = get_qr_styling(qr_code)
qr_code.qr_code_image = generate_qr_code(
data=qr_data,
@@ -323,38 +338,11 @@ def update_existing_qr_codes():
continue
if updated_count > 0:
_db.session.commit()
lh.logger.info(f"Updated {updated_count} existing QR codes with missing URLs/images")
lh.logger.info(f"Startup: updated {updated_count} QR codes with missing URLs/images")
except Exception as e:
lh.log_database_error('update_existing_qr_codes', e)
def log_slow_query_performance(app_instance):
"""Register slow-query monitoring hooks on the given app instance"""
from extensions import logger_handler as lh
@app_instance.before_request
def before_request():
g.start_time = _time.time()
@app_instance.after_request
def after_request(response):
if hasattr(g, 'start_time'):
duration = _time.time() - g.start_time
if duration > 2.0:
lh.log_system_event(
event_type="slow_query_detected",
description=f"Slow request: {request.endpoint} took {duration:.2f}s",
severity="WARNING",
additional_data={
'duration': duration,
'endpoint': request.endpoint,
'method': request.method,
'user': session.get('username', 'anonymous')
}
)
return response
# ---------------------------------------------------------------------------
# Entry point
# ---------------------------------------------------------------------------
@@ -365,7 +353,7 @@ if __name__ == '__main__':
with app.app_context():
try:
create_tables()
log_slow_query_performance(app)
update_existing_qr_codes()
from extensions import logger_handler
logger_handler.logger.info("Initializing performance optimizations")
+10
View File
@@ -33,6 +33,16 @@ class Config:
)
DEBUG = os.environ.get('DEBUG', 'False').lower() == 'true'
# ------------------------------------------------------------------ #
# SQLAlchemy connection pool (read from .env; safe defaults)
# ------------------------------------------------------------------ #
SQLALCHEMY_ENGINE_OPTIONS = {
'pool_size': int(os.environ.get('SQLALCHEMY_ENGINE_OPTIONS_POOL_SIZE', '10')),
'pool_timeout': int(os.environ.get('SQLALCHEMY_ENGINE_OPTIONS_POOL_TIMEOUT', '20')),
'pool_recycle': int(os.environ.get('SQLALCHEMY_ENGINE_OPTIONS_POOL_RECYCLE', '3600')),
'max_overflow': int(os.environ.get('SQLALCHEMY_ENGINE_OPTIONS_MAX_OVERFLOW', '20')),
}
# ------------------------------------------------------------------ #
# Session / cookies
# ------------------------------------------------------------------ #
+1 -1
View File
@@ -18,7 +18,7 @@ class AttendanceData(base.db.Model):
qr_code_id = base.db.Column(base.db.Integer, base.db.ForeignKey('qr_codes.id', ondelete='CASCADE'), nullable=False)
employee_id = base.db.Column(base.db.String(50), nullable=False)
check_in_date = base.db.Column(base.db.Date, nullable=False, default=datetime.today)
check_in_time = base.db.Column(base.db.Time, nullable=False, default=datetime.now().time)
check_in_time = base.db.Column(base.db.Time, nullable=False, default=lambda: datetime.now().time())
device_info = base.db.Column(base.db.String(200))
user_agent = base.db.Column(base.db.Text)
ip_address = base.db.Column(base.db.String(45))
+1
View File
@@ -53,6 +53,7 @@ class User(base.db.Model):
"""Check if user has staff-level permissions (includes new roles)"""
return self.role in STAFF_LEVEL_ROLES
@staticmethod
def has_export_permissions(user_role):
"""Check if user role has export permissions"""
return user_role in ['admin', 'payroll']
-39
View File
@@ -650,43 +650,4 @@ def get_miss_punch_details(employee_id):
'message': 'Internal server error. Please check the server logs.'
}), 500
def get_employee_name(employee_id):
"""Helper function to get employee full name by ID"""
try:
result = db.session.execute(text("""
SELECT CONCAT(firstName, ' ', lastName) as full_name
FROM employee
WHERE id = :employee_id
"""), {'employee_id': employee_id})
row = result.fetchone()
return row[0] if row else f"Employee {employee_id}"
except Exception as e:
logger_handler.logger.warning(f"Error getting employee name for ID {employee_id}: {e}")
return f"Employee {employee_id}"
def get_qr_code_checkin_count(qr_code_id):
"""Helper function to get total check-ins count for a QR code"""
try:
count = AttendanceData.query.filter_by(qr_code_id=qr_code_id).count()
logger_handler.logger.info(f"QR Code {qr_code_id} total check-ins: {count}")
return count
except Exception as e:
logger_handler.logger.error(f"Error getting check-ins count for QR {qr_code_id}: {e}")
return 0
@bp.context_processor
def inject_payroll_utils():
"""Inject payroll utility functions into templates"""
return {
'get_employee_name': get_employee_name,
'format_hours': lambda hours: f"{hours:.2f}" if hours else "0.00"
}
@bp.context_processor
def inject_dashboard_utils():
"""Inject dashboard utility functions into templates"""
return {
'get_qr_code_checkin_count': get_qr_code_checkin_count
}
-11
View File
@@ -11,17 +11,6 @@ Contains:
- export_time_attendance_excel() (single-employee / all-employees)
- export_time_attendance_by_building_excel()
"""
"""
routes/time_attendance.py
=========================
Time attendance dashboard, import pipeline, export (Excel / by-building),
and records management routes.
Routes: /time-attendance, /time-attendance/import/*,
/time-attendance/export*, /time-attendance/records,
/time-attendance/record/<id>, /time-attendance/delete/<id>,
/api/time-attendance/*
"""
from flask import Blueprint, render_template, request, redirect, flash, session, jsonify, send_file, Response, g, current_app, url_for
from datetime import datetime, date, timedelta, time
import io, os, json, re, uuid, traceback
+2 -2
View File
@@ -10,12 +10,12 @@ Management{% endblock %} {% block content %}
<p class="error-detail">This action requires administrator privileges.</p>
</div>
<div class="error-actions">
<a href="{{ url_for('dashboard') }}" class="btn btn-primary">
<a href="{{ url_for('dashboard.dashboard') }}" class="btn btn-primary">
<i class="fas fa-home"></i>
Go to Dashboard
</a>
{% if session.role == 'staff' %}
<a href="{{ url_for('profile') }}" class="btn btn-secondary">
<a href="{{ url_for('auth.profile') }}" class="btn btn-secondary">
<i class="fas fa-user"></i>
View Profile
</a>
+1 -1
View File
@@ -9,7 +9,7 @@ endblock %} {% block content %}
<p>The page you're looking for doesn't exist or has been moved.</p>
</div>
<div class="error-actions">
<a href="{{ url_for('dashboard') }}" class="btn btn-primary">
<a href="{{ url_for('dashboard.dashboard') }}" class="btn btn-primary">
<i class="fas fa-home"></i>
Go to Dashboard
</a>
+1 -1
View File
@@ -10,7 +10,7 @@ endblock %} {% block content %}
<p class="error-detail">Please try again in a few moments.</p>
</div>
<div class="error-actions">
<a href="{{ url_for('dashboard') }}" class="btn btn-primary">
<a href="{{ url_for('dashboard.dashboard') }}" class="btn btn-primary">
<i class="fas fa-home"></i>
Go to Dashboard
</a>
+10
View File
@@ -34,6 +34,16 @@ except Exception as e:
gmaps_client = None
print(f"❌ Error initializing Google Maps client: {e}")
def is_gmaps_available():
"""Return True if the Google Maps client is initialized and usable.
Always call this (or check ``if gmaps_client:``) before calling any
method on ``gmaps_client`` to prevent AttributeError when the API key
is absent.
"""
return gmaps_client is not None
# ---------------------------------------------------------------------------
# Geocoding cache
# ---------------------------------------------------------------------------