06/03 Optimize app
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -83,7 +83,7 @@
|
||||
|
||||
{% 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);">
|
||||
<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">
|
||||
{% 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>
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
|
||||
{% 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);">
|
||||
<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">
|
||||
{% 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>
|
||||
|
||||
@@ -120,7 +120,7 @@
|
||||
<!-- Pagination -->
|
||||
{% 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);">
|
||||
<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">
|
||||
{% 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>
|
||||
|
||||
Reference in New Issue
Block a user