diff --git a/app/routes/auth.py b/app/routes/auth.py index fd3e4ac..8d617d7 100644 --- a/app/routes/auth.py +++ b/app/routes/auth.py @@ -3,8 +3,11 @@ from flask_login import login_user, logout_user, login_required, current_user from urllib.parse import urlparse from app import db from app.models.user import User -from app.utils.forms import LoginForm, UserForm +from app.utils.forms import LoginForm, UserForm, ProfileForm from app.utils.decorators import admin_required +import logging + +logger = logging.getLogger(__name__) bp = Blueprint('auth', __name__, url_prefix='/auth') @@ -55,6 +58,57 @@ def logout(): return redirect(url_for('auth.login')) +@bp.route('/profile', methods=['GET', 'POST']) +@login_required +def profile(): + """User profile page — view stats and update email/password.""" + from app.models.inspection import Inspection + from app.models.issue import Issue + + form = ProfileForm(user=current_user, obj=current_user) + + if form.validate_on_submit(): + current_user.email = form.email.data + + if form.new_password.data: + current_user.set_password(form.new_password.data) + logger.info('AUTH | profile_password_change | user_id=%s username=%s', + current_user.id, current_user.username) + + db.session.commit() + logger.info('AUTH | profile_update | user_id=%s username=%s email=%s', + current_user.id, current_user.username, current_user.email) + flash('Profile updated successfully.', 'success') + return redirect(url_for('auth.profile')) + + # ── Activity stats ──────────────────────────────────────────────────── + total_inspections = Inspection.query.filter_by(inspector_id=current_user.id).count() + completed_inspections = Inspection.query.filter_by( + inspector_id=current_user.id, status='completed' + ).count() + + recent_inspections = ( + Inspection.query + .filter_by(inspector_id=current_user.id) + .order_by(Inspection.inspection_date.desc()) + .limit(5) + .all() + ) + + open_issues = Issue.query.filter_by( + assigned_to=current_user.id, status='open' + ).count() if hasattr(Issue, 'assigned_to') else 0 + + return render_template( + 'auth/profile.html', + form=form, + total_inspections=total_inspections, + completed_inspections=completed_inspections, + recent_inspections=recent_inspections, + open_issues=open_issues, + ) + + @bp.route('/users') @login_required @admin_required @@ -78,6 +132,8 @@ def create_user(): user.set_password(form.password.data) db.session.add(user) db.session.commit() + logger.info('AUTH | user_create | admin_id=%s admin=%s new_user=%s role=%s', + current_user.id, current_user.username, user.username, user.role) flash(f'User {user.username} created successfully.', 'success') return redirect(url_for('auth.list_users')) @@ -100,6 +156,8 @@ def edit_user(user_id): user.set_password(form.password.data) db.session.commit() + logger.info('AUTH | user_edit | admin_id=%s admin=%s target_user_id=%s target_user=%s', + current_user.id, current_user.username, user.id, user.username) flash(f'User {user.username} updated successfully.', 'success') return redirect(url_for('auth.list_users')) @@ -130,5 +188,7 @@ def delete_user(user_id): username = user.username db.session.delete(user) db.session.commit() + logger.info('AUTH | user_delete | admin_id=%s admin=%s deleted_user=%s', + current_user.id, current_user.username, username) flash(f'User {username} deleted successfully.', 'success') - return redirect(url_for('auth.list_users')) + return redirect(url_for('auth.list_users')) \ No newline at end of file diff --git a/app/templates/auth/profile.html b/app/templates/auth/profile.html new file mode 100644 index 0000000..a86faf0 --- /dev/null +++ b/app/templates/auth/profile.html @@ -0,0 +1,212 @@ +{% extends "base.html" %} + +{% block title %}My Profile{% endblock %} + +{% block content %} +
+
+

My Profile

+

Manage your account information and review your activity.

+
+
+ +
+ + +
+ + +
+
+
+ + + +
+

{{ current_user.username }}

+

{{ current_user.email }}

+ + + {{ current_user.role | title }} + +
+ + + Member since {{ current_user.created_at.strftime('%B %d, %Y') }} + +
+
+ + +
+
+
Activity Summary
+
+
+
+
+
+
{{ total_inspections }}
+ Total Inspections +
+
+
+
+
{{ completed_inspections }}
+ Completed +
+
+
+
+
{{ open_issues }}
+ Open Issues +
+
+
+
+ {% if total_inspections > 0 %} +
+ {{ ((completed_inspections / total_inspections) * 100) | int }}% +
+ {% else %} +
+ {% endif %} + Completion Rate +
+
+
+
+
+ +
+ + +
+ + +
+
+
Edit Profile
+
+
+
+ {{ form.hidden_tag() }} + + +
+ {{ form.email.label(class="form-label fw-semibold") }} + {{ form.email(class="form-control" + (" is-invalid" if form.email.errors else ""), + placeholder="your@email.com") }} + {% for error in form.email.errors %} +
{{ error }}
+ {% endfor %} +
+ +
+

+ Leave the password fields blank to keep your current password. +

+ + +
+ {{ form.current_password.label(class="form-label fw-semibold") }} + {{ form.current_password(class="form-control" + (" is-invalid" if form.current_password.errors else ""), + autocomplete="current-password") }} + {% for error in form.current_password.errors %} +
{{ error }}
+ {% endfor %} +
+ + +
+ {{ form.new_password.label(class="form-label fw-semibold") }} + {{ form.new_password(class="form-control" + (" is-invalid" if form.new_password.errors else ""), + autocomplete="new-password") }} + {% for error in form.new_password.errors %} +
{{ error }}
+ {% endfor %} +
Minimum 6 characters.
+
+ + +
+ {{ form.confirm_password.label(class="form-label fw-semibold") }} + {{ form.confirm_password(class="form-control" + (" is-invalid" if form.confirm_password.errors else ""), + autocomplete="new-password") }} + {% for error in form.confirm_password.errors %} +
{{ error }}
+ {% endfor %} +
+ + +
+
+
+ + +
+
+
Recent Inspections
+ + View All + +
+
+ {% if recent_inspections %} +
+ + + + + + + + + + + + + {% for insp in recent_inspections %} + + + + + + + + + {% endfor %} + +
DateFacilityTemplateScoreStatus
{{ insp.inspection_date.strftime('%Y-%m-%d') }}{{ insp.facility.name if insp.facility else '—' }}{{ insp.template.name if insp.template else '—' }} + {% if insp.overall_score is not none %} + + {{ "%.1f"|format(insp.overall_score) }}% + + {% else %} + + {% endif %} + + + {{ insp.status | replace('_', ' ') | title }} + + + + + +
+
+ {% else %} +
+ + No inspections recorded yet. +
+ {% endif %} +
+
+ +
+
+{% endblock %} \ No newline at end of file diff --git a/app/templates/base.html b/app/templates/base.html index 68009d1..e94c67e 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -139,6 +139,13 @@ {{ current_user.username }}