Upload floor-map blueprints (light/dark) instead of typing a path
The Floor Map settings only had text fields for the blueprint image path with no way to upload one. Add a real upload. - POST /settings/map-blueprint (multipart: file + theme) saves the image to the instance maps dir and points map_blueprint_<theme> at the served URL; GET /settings/map-blueprint/<file> serves it (public - kiosks read the map). - Settings > Floor Map: a file picker + thumbnail next to each theme's field (still accepts a manual path/URL too). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,10 @@
|
||||
"""Settings API routes."""
|
||||
|
||||
from flask import Blueprint, request
|
||||
import os
|
||||
|
||||
from flask import Blueprint, request, current_app, send_from_directory
|
||||
from flask_jwt_extended import jwt_required
|
||||
from werkzeug.utils import secure_filename
|
||||
|
||||
from shopdb.extensions import db, cache
|
||||
from shopdb.core.models import Setting, AuditLog
|
||||
@@ -11,6 +14,14 @@ from shopdb.utils.authz import require_permission, require_role
|
||||
|
||||
settings_bp = Blueprint('settings', __name__)
|
||||
|
||||
# Floor-map blueprint uploads live in the instance dir and are served publicly
|
||||
# (the kiosk dashboards read them without auth).
|
||||
MAP_IMAGE_EXTENSIONS = {'.png', '.jpg', '.jpeg', '.gif', '.webp', '.svg'}
|
||||
|
||||
|
||||
def _map_dir():
|
||||
return os.path.join(current_app.instance_path, 'maps')
|
||||
|
||||
# Cache key for settings
|
||||
SETTINGS_CACHE_KEY = 'system_settings'
|
||||
SETTINGS_CACHE_TTL = 300 # 5 minutes
|
||||
@@ -74,6 +85,48 @@ def invalidate_settings_cache():
|
||||
cache.delete(SETTINGS_CACHE_KEY)
|
||||
|
||||
|
||||
@settings_bp.route('/map-blueprint', methods=['POST'])
|
||||
@jwt_required()
|
||||
@require_role('admin')
|
||||
def upload_map_blueprint():
|
||||
"""Upload a floor-map blueprint image and point the setting at it.
|
||||
|
||||
multipart/form-data: file=<image>, theme=light|dark. Saves to the instance
|
||||
maps dir and sets map_blueprint_<theme> to the served URL.
|
||||
"""
|
||||
theme = (request.form.get('theme') or '').strip().lower()
|
||||
if theme not in ('light', 'dark'):
|
||||
return error_response(ErrorCodes.VALIDATION_ERROR, 'theme must be light or dark')
|
||||
upload = request.files.get('file')
|
||||
if not upload or not upload.filename:
|
||||
return error_response(ErrorCodes.VALIDATION_ERROR, 'No file provided')
|
||||
ext = os.path.splitext(upload.filename)[1].lower()
|
||||
if ext not in MAP_IMAGE_EXTENSIONS:
|
||||
return error_response(ErrorCodes.VALIDATION_ERROR,
|
||||
f'Unsupported image type {ext}')
|
||||
|
||||
os.makedirs(_map_dir(), exist_ok=True)
|
||||
filename = secure_filename(f'blueprint-{theme}{ext}')
|
||||
upload.save(os.path.join(_map_dir(), filename))
|
||||
|
||||
url = f'/api/settings/map-blueprint/{filename}'
|
||||
key = f'map_blueprint_{theme}'
|
||||
setting = Setting.query.filter_by(key=key).first()
|
||||
if setting:
|
||||
setting.value = url
|
||||
else:
|
||||
db.session.add(Setting(key=key, value=url, valuetype='string', category='map'))
|
||||
db.session.commit()
|
||||
invalidate_settings_cache()
|
||||
return success_response({'key': key, 'value': url}, message='Blueprint uploaded')
|
||||
|
||||
|
||||
@settings_bp.route('/map-blueprint/<path:filename>', methods=['GET'])
|
||||
def serve_map_blueprint(filename):
|
||||
"""Serve an uploaded blueprint image (public - kiosks read it)."""
|
||||
return send_from_directory(_map_dir(), filename)
|
||||
|
||||
|
||||
@settings_bp.route('', methods=['GET'])
|
||||
@jwt_required(optional=True)
|
||||
def list_settings():
|
||||
|
||||
Reference in New Issue
Block a user