06/03 Optimize app
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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))
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 }} · {{ ((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>
|
||||||
|
|||||||
@@ -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 }} · {{ 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>
|
||||||
|
|||||||
@@ -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 }} · {{ ((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>
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user