diff --git a/app/routes/tenant_settings.py b/app/routes/tenant_settings.py index 6aba584..b03df9a 100644 --- a/app/routes/tenant_settings.py +++ b/app/routes/tenant_settings.py @@ -29,6 +29,7 @@ from app.models.tenant_settings import TenantSettings from app.utils.decorators import admin_required from app.utils.audit import log_action, ACTION_UPDATE, ACTION_CREATE, ACTION_DELETE from app.utils.time_utils import now_eastern +from app.utils import storage logger = logging.getLogger(__name__) @@ -43,7 +44,19 @@ _DOMAIN_RE = re.compile( # ── helpers ─────────────────────────────────────────────────────────────────── def _save_logo(file_obj): - """Save uploaded logo to static/uploads/logos/; return relative URL or None.""" + """Save an uploaded logo via the storage seam; return its key or None. + + MT-22: this previously wrote straight to ``UPLOAD_FOLDER/logos/`` with + ``file_obj.save()`` — the last direct-to-disk writer in the app. On an + R2-backed tenant the logo never reached the bucket, and on the local + backend every tenant's logo landed in one shared directory. + + Validation stays here by design: ``storage.py`` only moves bytes, and + callers own extension / magic-byte checks (see its module docstring). + + The returned key keeps the exact ``uploads/logos/`` shape already + stored in ``TenantSettings.logo_url``, so no migration and no data rewrite. + """ if not file_obj or not file_obj.filename: return None allowed = {'png', 'jpg', 'jpeg', 'gif', 'svg', 'webp'} @@ -63,27 +76,22 @@ def _save_logo(file_obj): } if not any(header.startswith(m) for m in magic): return None - import secrets - logos_dir = os.path.join(current_app.config['UPLOAD_FOLDER'], 'logos') - os.makedirs(logos_dir, exist_ok=True) - filename = f'{secrets.token_hex(12)}.{ext}' - file_obj.save(os.path.join(logos_dir, filename)) - return f'uploads/logos/{filename}' + file_obj.seek(0) + return storage.save(file_obj, 'logos') def _delete_logo(logo_url): - """Remove a logo file from disk. Silently ignores missing files. - Safety guard: only deletes files inside the uploads/logos/ subfolder.""" + """Remove a stored logo. Silently ignores missing objects. + Safety guard: only deletes keys inside the uploads/logos/ subfolder.""" + if not logo_url: + return # nothing stored — never issue a delete for 'uploads/logos/' try: # Use only the basename to avoid any path-traversal via the stored URL. - # All logos are written into logos_dir by _save_logo(), so joining the - # basename back to that directory is always the correct path. - logos_dir = os.path.join(current_app.config['UPLOAD_FOLDER'], 'logos') - abs_logos = os.path.abspath(logos_dir) - abs_path = os.path.join(abs_logos, os.path.basename(logo_url)) - if os.path.isfile(abs_path): - os.remove(abs_path) - logger.info('SETTINGS | logo_deleted | path=%s', abs_path) + # All logos are written to 'uploads/logos/' by _save_logo(), so + # rebuilding the key from the basename is always the correct target. + key = f'uploads/logos/{os.path.basename(str(logo_url or ""))}' + storage.delete(key) + logger.info('SETTINGS | logo_deleted | key=%s', key) except Exception as exc: logger.warning('SETTINGS | logo_delete_failed | url=%s err=%s', logo_url, exc) diff --git a/app/templates/layouts/classic.html b/app/templates/layouts/classic.html index de45ba2..99563c3 100644 --- a/app/templates/layouts/classic.html +++ b/app/templates/layouts/classic.html @@ -79,7 +79,7 @@
{% if tenant_branding and tenant_branding.logo_url %} - {{ tenant_branding.display_name }} {% else %} diff --git a/app/templates/layouts/modern.html b/app/templates/layouts/modern.html index 9e2afb2..e504782 100644 --- a/app/templates/layouts/modern.html +++ b/app/templates/layouts/modern.html @@ -106,7 +106,7 @@ {% if tenant_branding and tenant_branding.logo_url %} - {% else %} JQC diff --git a/app/templates/tenant_settings/branding.html b/app/templates/tenant_settings/branding.html index 7244ee6..960425b 100644 --- a/app/templates/tenant_settings/branding.html +++ b/app/templates/tenant_settings/branding.html @@ -38,7 +38,7 @@ {% if settings.logo_url %}
- Current logo
@@ -105,7 +105,7 @@