06/03 Optimize app

This commit is contained in:
2026-06-03 16:43:39 -04:00
parent 3990a01079
commit 3abd073437
9 changed files with 36 additions and 6 deletions
+13
View File
@@ -78,6 +78,19 @@ def create_app(config_name=None):
csrf.init_app(app) csrf.init_app(app)
limiter.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.auth import auth_bp
from app.routes.dashboard import dashboard_bp from app.routes.dashboard import dashboard_bp
from app.routes.accounts import accounts_bp from app.routes.accounts import accounts_bp
+6
View File
@@ -36,6 +36,12 @@ class Config:
SCHWAB_REDIRECT_URI = os.environ.get('SCHWAB_REDIRECT_URI', SCHWAB_REDIRECT_URI = os.environ.get('SCHWAB_REDIRECT_URI',
'https://pfm.ngodanguyen.tech/schwab/callback') '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) # Budget alert emails (optional — all four must be set to enable)
SMTP_HOST = os.environ.get('SMTP_HOST', '') SMTP_HOST = os.environ.get('SMTP_HOST', '')
SMTP_PORT = int(os.environ.get('SMTP_PORT', 587)) SMTP_PORT = int(os.environ.get('SMTP_PORT', 587))
+1 -1
View File
@@ -149,7 +149,7 @@ def map_accounts():
for sa in schwab_accounts: for sa in schwab_accounts:
val = request.form.get(f'pfm_account_{sa.id}', '') val = request.form.get(f'pfm_account_{sa.id}', '')
if val == 'new': 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( new_acct = Account(
name=sa.account_name, name=sa.account_name,
account_type=pfm_type, account_type=pfm_type,
+2
View File
@@ -3,6 +3,7 @@ import uuid
from flask import (Blueprint, render_template, redirect, url_for, flash, from flask import (Blueprint, render_template, redirect, url_for, flash,
request, current_app, send_from_directory) request, current_app, send_from_directory)
from flask_login import login_required, current_user from flask_login import login_required, current_user
from app.utils.audit import audit
from flask_wtf import FlaskForm from flask_wtf import FlaskForm
from flask_wtf.file import FileField, FileAllowed from flask_wtf.file import FileField, FileAllowed
from wtforms import (StringField, SelectField, PasswordField, SubmitField, from wtforms import (StringField, SelectField, PasswordField, SubmitField,
@@ -171,6 +172,7 @@ def password():
else: else:
current_user.set_password(form.new_password.data) current_user.set_password(form.new_password.data)
db.session.commit() db.session.commit()
audit('password_changed', f'user={current_user.username}')
flash('Password changed successfully.', 'success') flash('Password changed successfully.', 'success')
return redirect(url_for('settings.profile')) return redirect(url_for('settings.profile'))
return render_template('settings/password.html', form=form) return render_template('settings/password.html', form=form)
+7
View File
@@ -55,6 +55,13 @@ CATEGORY_MAP = {
ACCOUNT_TYPE_MAP = { ACCOUNT_TYPE_MAP = {
'CASH': 'checking', 'CASH': 'checking',
'MARGIN': 'investment', '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 # Schwab instrument asset type → PFM investment asset type
+1 -1
View File
@@ -83,7 +83,7 @@
{% if pagination.pages > 1 %} {% if pagination.pages > 1 %}
<div class="d-flex justify-content-between align-items-center px-4 py-3" style="border-top:1px solid var(--border);font-size:12px;color:var(--muted);"> <div class="d-flex justify-content-between align-items-center px-4 py-3" style="border-top:1px solid var(--border);font-size:12px;color:var(--muted);">
<span>{{ ((pagination.page-1)*30)+1 }}{{ [pagination.page*30, pagination.total]|min }} of {{ pagination.total }}</span> <span>Page {{ pagination.page }} of {{ pagination.pages }} &nbsp;·&nbsp; {{ ((pagination.page-1)*30)+1 }}{{ [pagination.page*30, pagination.total]|min }} of {{ pagination.total }}</span>
<div class="d-flex gap-1"> <div class="d-flex gap-1">
{% if pagination.has_prev %} {% if pagination.has_prev %}
<a href="{{ url_for('accounts.payments', id=account.id, page=pagination.prev_num) }}" class="btn btn-sm btn-outline-secondary" style="font-size:11px;">← Prev</a> <a href="{{ url_for('accounts.payments', id=account.id, page=pagination.prev_num) }}" class="btn btn-sm btn-outline-secondary" style="font-size:11px;">← Prev</a>
+1 -1
View File
@@ -48,7 +48,7 @@
{% if insights.pages > 1 %} {% if insights.pages > 1 %}
<div class="d-flex justify-content-between align-items-center px-4 py-3" style="border-top:1px solid var(--border);font-size:12px;color:var(--muted);"> <div class="d-flex justify-content-between align-items-center px-4 py-3" style="border-top:1px solid var(--border);font-size:12px;color:var(--muted);">
<span>Page {{ insights.page }} of {{ insights.pages }}</span> <span>Page {{ insights.page }} of {{ insights.pages }} &nbsp;·&nbsp; {{ insights.total }} entries</span>
<div class="d-flex gap-1"> <div class="d-flex gap-1">
{% if insights.has_prev %} {% if insights.has_prev %}
<a href="{{ url_for('ai.history', page=insights.prev_num) }}" class="btn btn-sm btn-outline-secondary" style="font-size:11px;">← Prev</a> <a href="{{ url_for('ai.history', page=insights.prev_num) }}" class="btn btn-sm btn-outline-secondary" style="font-size:11px;">← Prev</a>
+1 -1
View File
@@ -120,7 +120,7 @@
<!-- Pagination --> <!-- Pagination -->
{% if pagination.pages > 1 %} {% if pagination.pages > 1 %}
<div class="d-flex justify-content-between align-items-center px-4 py-3" style="border-top:1px solid var(--border);font-size:12px;color:var(--muted);"> <div class="d-flex justify-content-between align-items-center px-4 py-3" style="border-top:1px solid var(--border);font-size:12px;color:var(--muted);">
<span>Showing {{ ((pagination.page-1)*30)+1 }}{{ [pagination.page*30, pagination.total]|min }} of {{ pagination.total }}</span> <span>Page {{ pagination.page }} of {{ pagination.pages }} &nbsp;·&nbsp; {{ ((pagination.page-1)*30)+1 }}{{ [pagination.page*30, pagination.total]|min }} of {{ pagination.total }}</span>
<div class="d-flex gap-1"> <div class="d-flex gap-1">
{% if pagination.has_prev %} {% if pagination.has_prev %}
<a href="{{ url_for('transactions.index', tab=tab, page=pagination.prev_num, q=search, category_id=category_id, account_id=account_id, date_from=date_from, date_to=date_to) }}" class="btn btn-sm btn-outline-secondary" style="font-size:11px;">← Prev</a> <a href="{{ url_for('transactions.index', tab=tab, page=pagination.prev_num, q=search, category_id=category_id, account_id=account_id, date_from=date_from, date_to=date_to) }}" class="btn btn-sm btn-outline-secondary" style="font-size:11px;">← Prev</a>
+2
View File
@@ -20,3 +20,5 @@ pdfplumber==0.11.4
flask-limiter==3.5.0 flask-limiter==3.5.0
pyotp==2.9.0 pyotp==2.9.0
qrcode==7.4.2 qrcode==7.4.2
# Monitoring
sentry-sdk[flask]==2.7.0