From 253058b45b619289967608f0538feffb23b5075c Mon Sep 17 00:00:00 2001 From: Matt Hills Date: Mon, 20 Oct 2025 17:40:04 -0400 Subject: [PATCH] Add QR code functionality for AR template URLs - Add QR code icon next to each tenant's AR template URL on main page - Add modal popup to display dynamically generated QR codes - Add routes for QR code generation (/qrcode/template and /qrcode/arinfo) - Include Bootstrap Icons for QR code icon display --- src/app.py | 100 ++++++++++++++++++++++++++-- src/templates/tenant_selection.html | 35 +++++++++- src/templates/tenant_settings.html | 29 +++++++- 3 files changed, 154 insertions(+), 10 deletions(-) diff --git a/src/app.py b/src/app.py index 9ba7d84..7228860 100644 --- a/src/app.py +++ b/src/app.py @@ -146,23 +146,64 @@ def get_ar_content_fields(tenant_id): fields = database.get_custom_ar_fields(tenant_id) return jsonify(fields), 200 -@app.route('//arinfo', methods=['GET']) +@app.route('//arinfo', methods=['GET', 'POST']) def get_ar_info(tenant_id): barcode = request.args.get('barcode') products = load_products(tenant_id) - + # Get custom AR fields for this tenant custom_fields = database.get_custom_ar_fields(tenant_id) custom_field_names = [f['fieldName'] for f in custom_fields] - + + # Handle POST request - update product fields + if request.method == 'POST': + if not barcode: + return jsonify({"error": "Barcode parameter required"}), 400 + + if barcode not in products: + return jsonify({"error": "Product not found"}), 404 + + try: + # Get the updated fields from the request body + updated_fields = request.get_json() + + if not isinstance(updated_fields, list): + return jsonify({"error": "Request body must be an array of fields"}), 400 + + # Get current product data + current_product = products[barcode] + + # Update only the editable fields + for updated_field in updated_fields: + field_name = updated_field.get('fieldName') + new_value = updated_field.get('value') + + # Find the field in the current product and update it + for field in current_product: + if field['fieldName'] == field_name: + # Check if the field is editable + if field.get('editable') == 'true': + field['value'] = new_value + break + + # Save the updated product to database + database.save_product(barcode, tenant_id, current_product) + + app.logger.info(f"Updated product {barcode} for tenant {tenant_id}") + return jsonify({"success": True}), 200 + + except Exception as e: + app.logger.error(f"Error updating product: {str(e)}") + return jsonify({"error": "Failed to update product"}), 500 + # Helper function to convert relative image paths to absolute URLs and filter fields def filter_and_process_fields(product_fields): # Filter to only include fields defined in custom AR fields filtered_fields = [] - + # Get field types for all custom fields field_types = {f['fieldName']: f['fieldType'] for f in custom_fields} - + for field in product_fields: if field['fieldName'] in custom_field_names: # Create absolute URL for IMAGE_URI fields @@ -173,7 +214,8 @@ def get_ar_info(tenant_id): field['value'] = f"{request.url_root.rstrip('/')}/{tenant_id}{field['value']}" filtered_fields.append(field) return filtered_fields - + + # Handle GET request - return product data # If barcode is provided, return specific product if barcode: if barcode in products: @@ -182,7 +224,7 @@ def get_ar_info(tenant_id): response.headers['Access-Control-Allow-Origin'] = '*' return response, 200 return jsonify({"error": "Product not found"}), 404 - + # Return all products if no barcode specified # Convert all products to have absolute URLs and filter fields all_products = {} @@ -241,6 +283,50 @@ def serve_image(tenant_id, filename): app.logger.warning(f"Image not found: {filename}") return jsonify({"error": "Image not found"}), 404 +@app.route('//qrcode/template', methods=['GET']) +def generate_template_qr_code(tenant_id): + """Generate QR code for the AR Template URL""" + try: + # Get server URL from settings + server_url = database.get_server_url() + template_url = f"{server_url}/{tenant_id}/" + + # Generate QR code + buffer = BytesIO() + qr = qrcode.QRCode(version=1, box_size=10, border=5) + qr.add_data(template_url) + qr.make(fit=True) + img = qr.make_image(fill_color="black", back_color="white") + img.save(buffer, format='PNG') + buffer.seek(0) + + return Response(buffer.getvalue(), mimetype='image/png') + except Exception as e: + app.logger.error(f"QR code generation error: {str(e)}") + return jsonify({"error": "Failed to generate QR code"}), 500 + +@app.route('//qrcode/arinfo', methods=['GET']) +def generate_ar_qr_code(tenant_id): + """Generate QR code for the AR API endpoint""" + try: + # Get server URL from settings + server_url = database.get_server_url() + ar_url = f"{server_url}/{tenant_id}/arinfo" + + # Generate QR code + buffer = BytesIO() + qr = qrcode.QRCode(version=1, box_size=10, border=5) + qr.add_data(ar_url) + qr.make(fit=True) + img = qr.make_image(fill_color="black", back_color="white") + img.save(buffer, format='PNG') + buffer.seek(0) + + return Response(buffer.getvalue(), mimetype='image/png') + except Exception as e: + app.logger.error(f"QR code generation error: {str(e)}") + return jsonify({"error": "Failed to generate QR code"}), 500 + @app.route('//barcodes/', methods=['GET']) def serve_barcode(tenant_id, filename): # Parse filename to extract product_id and barcode type diff --git a/src/templates/tenant_selection.html b/src/templates/tenant_selection.html index 5e2012f..14ee745 100644 --- a/src/templates/tenant_selection.html +++ b/src/templates/tenant_selection.html @@ -5,6 +5,7 @@ Select or Create Tenant - KCAP Demo Server +
@@ -38,9 +39,15 @@ {{ tenant.created_at }}

Username: {{ tenant.username }}

-

Knox Capture AR Template URL: {{ server_url }}/{{ tenant.id }}/

+

+ Knox Capture AR Template URL: + {{ server_url }}/{{ tenant.id }}/ +

ID: {{ tenant.id }} +
@@ -79,7 +86,31 @@ - + + + {% for tenant in tenants %} + + {% endfor %} +