diff --git a/app/__init__.py b/app/__init__.py index af04b56..83a4a2b 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -78,6 +78,19 @@ def create_app(config_name=None): csrf.init_app(app) limiter.init_app(app) + # ── Sentry (optional) ──────────────────────────────────────────────────── + sentry_dsn = app.config.get('SENTRY_DSN', '') + if sentry_dsn: + import sentry_sdk + from sentry_sdk.integrations.flask import FlaskIntegration + sentry_sdk.init( + dsn=sentry_dsn, + integrations=[FlaskIntegration()], + traces_sample_rate=0.1, # 10 % of requests traced + send_default_pii=False, # no personal data in error reports + ) + logging.getLogger('app').info('Sentry initialised') + from app.routes.auth import auth_bp from app.routes.dashboard import dashboard_bp from app.routes.accounts import accounts_bp diff --git a/app/config.py b/app/config.py index b9204b7..d325e95 100644 --- a/app/config.py +++ b/app/config.py @@ -36,6 +36,12 @@ class Config: SCHWAB_REDIRECT_URI = os.environ.get('SCHWAB_REDIRECT_URI', 'https://pfm.ngodanguyen.tech/schwab/callback') + # Sentry error monitoring (optional — leave blank to disable) + SENTRY_DSN = os.environ.get('SENTRY_DSN', '') + + # Session idle timeout in minutes (default 60) + SESSION_IDLE_MINUTES = int(os.environ.get('SESSION_IDLE_MINUTES', 60)) + # Budget alert emails (optional — all four must be set to enable) SMTP_HOST = os.environ.get('SMTP_HOST', '') SMTP_PORT = int(os.environ.get('SMTP_PORT', 587)) diff --git a/app/routes/schwab.py b/app/routes/schwab.py index 9f23ad3..16bf9e4 100644 --- a/app/routes/schwab.py +++ b/app/routes/schwab.py @@ -149,7 +149,7 @@ def map_accounts(): for sa in schwab_accounts: val = request.form.get(f'pfm_account_{sa.id}', '') if val == 'new': - pfm_type = ACCOUNT_TYPE_MAP.get(sa.account_type, 'other') + pfm_type = ACCOUNT_TYPE_MAP.get(sa.account_type, 'investment') new_acct = Account( name=sa.account_name, account_type=pfm_type, diff --git a/app/routes/settings.py b/app/routes/settings.py index 61a6475..eb1d0d0 100644 --- a/app/routes/settings.py +++ b/app/routes/settings.py @@ -3,6 +3,7 @@ import uuid from flask import (Blueprint, render_template, redirect, url_for, flash, request, current_app, send_from_directory) from flask_login import login_required, current_user +from app.utils.audit import audit from flask_wtf import FlaskForm from flask_wtf.file import FileField, FileAllowed from wtforms import (StringField, SelectField, PasswordField, SubmitField, @@ -171,6 +172,7 @@ def password(): else: current_user.set_password(form.new_password.data) db.session.commit() + audit('password_changed', f'user={current_user.username}') flash('Password changed successfully.', 'success') return redirect(url_for('settings.profile')) return render_template('settings/password.html', form=form) diff --git a/app/services/schwab_service.py b/app/services/schwab_service.py index 6303380..1b27130 100644 --- a/app/services/schwab_service.py +++ b/app/services/schwab_service.py @@ -53,8 +53,15 @@ CATEGORY_MAP = { # Schwab account type → PFM account type ACCOUNT_TYPE_MAP = { - 'CASH': 'checking', - 'MARGIN': 'investment', + 'CASH': 'checking', + 'MARGIN': 'investment', + 'IRA': 'investment', + 'ROTH_IRA': 'investment', + 'ROLLOVER_IRA': 'investment', + 'TRADITIONAL_IRA': 'investment', + '401K': 'investment', + 'ROTH_401K': 'investment', + 'BROKERAGE': 'investment', } # Schwab instrument asset type → PFM investment asset type diff --git a/app/templates/accounts/payments.html b/app/templates/accounts/payments.html index 4ce04da..603139a 100644 --- a/app/templates/accounts/payments.html +++ b/app/templates/accounts/payments.html @@ -83,7 +83,7 @@ {% if pagination.pages > 1 %}
- {{ ((pagination.page-1)*30)+1 }}–{{ [pagination.page*30, pagination.total]|min }} of {{ pagination.total }} + Page {{ pagination.page }} of {{ pagination.pages }}  ·  {{ ((pagination.page-1)*30)+1 }}–{{ [pagination.page*30, pagination.total]|min }} of {{ pagination.total }}
{% if pagination.has_prev %} ← Prev diff --git a/app/templates/ai/history.html b/app/templates/ai/history.html index 6bf3eb2..411c6e9 100644 --- a/app/templates/ai/history.html +++ b/app/templates/ai/history.html @@ -48,7 +48,7 @@ {% if insights.pages > 1 %}
- Page {{ insights.page }} of {{ insights.pages }} + Page {{ insights.page }} of {{ insights.pages }}  ·  {{ insights.total }} entries
{% if insights.has_prev %} ← Prev diff --git a/app/templates/transactions/index.html b/app/templates/transactions/index.html index 9e18c1a..303cb0f 100644 --- a/app/templates/transactions/index.html +++ b/app/templates/transactions/index.html @@ -120,7 +120,7 @@ {% if pagination.pages > 1 %}
- Showing {{ ((pagination.page-1)*30)+1 }}–{{ [pagination.page*30, pagination.total]|min }} of {{ pagination.total }} + Page {{ pagination.page }} of {{ pagination.pages }}  ·  {{ ((pagination.page-1)*30)+1 }}–{{ [pagination.page*30, pagination.total]|min }} of {{ pagination.total }}
{% if pagination.has_prev %} ← Prev diff --git a/requirements.txt b/requirements.txt index 176baa3..ad2bed4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -20,3 +20,5 @@ pdfplumber==0.11.4 flask-limiter==3.5.0 pyotp==2.9.0 qrcode==7.4.2 +# Monitoring +sentry-sdk[flask]==2.7.0