case insensitivity
This commit is contained in:
@@ -8,6 +8,7 @@ from barcode.writer import ImageWriter
|
|||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
import shutil
|
import shutil
|
||||||
from werkzeug.routing import BaseConverter
|
from werkzeug.routing import BaseConverter
|
||||||
|
from functools import wraps
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
# Set DATA_FOLDER to the absolute path of the data directory inside src
|
# Set DATA_FOLDER to the absolute path of the data directory inside src
|
||||||
@@ -18,6 +19,14 @@ app.config['SECRET_KEY'] = 'dev-key-for-demo-only'
|
|||||||
class TenantConverter(BaseConverter):
|
class TenantConverter(BaseConverter):
|
||||||
regex = '[a-zA-Z0-9_-]+'
|
regex = '[a-zA-Z0-9_-]+'
|
||||||
|
|
||||||
|
def to_python(self, value):
|
||||||
|
# Convert to lowercase when parsing from URL
|
||||||
|
return value.lower()
|
||||||
|
|
||||||
|
def to_url(self, value):
|
||||||
|
# Convert to lowercase when generating URLs
|
||||||
|
return value.lower()
|
||||||
|
|
||||||
app.url_map.converters['tenant'] = TenantConverter
|
app.url_map.converters['tenant'] = TenantConverter
|
||||||
|
|
||||||
# Load product data from database
|
# Load product data from database
|
||||||
|
|||||||
@@ -204,6 +204,10 @@ def migrate_from_json():
|
|||||||
|
|
||||||
def get_all_products(tenant_id: str = None) -> Dict[str, List[Dict[str, Any]]]:
|
def get_all_products(tenant_id: str = None) -> Dict[str, List[Dict[str, Any]]]:
|
||||||
"""Get all products for a tenant in the legacy format"""
|
"""Get all products for a tenant in the legacy format"""
|
||||||
|
# Normalize tenant_id to lowercase if provided
|
||||||
|
if tenant_id:
|
||||||
|
tenant_id = tenant_id.lower()
|
||||||
|
|
||||||
with get_db() as conn:
|
with get_db() as conn:
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
|
|
||||||
@@ -248,6 +252,9 @@ def get_all_products(tenant_id: str = None) -> Dict[str, List[Dict[str, Any]]]:
|
|||||||
|
|
||||||
def get_product(product_id: str, tenant_id: str) -> Optional[List[Dict[str, Any]]]:
|
def get_product(product_id: str, tenant_id: str) -> Optional[List[Dict[str, Any]]]:
|
||||||
"""Get a single product by ID and tenant"""
|
"""Get a single product by ID and tenant"""
|
||||||
|
# Normalize tenant_id to lowercase
|
||||||
|
tenant_id = tenant_id.lower()
|
||||||
|
|
||||||
with get_db() as conn:
|
with get_db() as conn:
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
|
|
||||||
@@ -272,6 +279,9 @@ def get_product(product_id: str, tenant_id: str) -> Optional[List[Dict[str, Any]
|
|||||||
|
|
||||||
def save_product(product_id: str, tenant_id: str, fields: List[Dict[str, Any]], image_data: Optional[bytes] = None, image_mime_type: Optional[str] = None):
|
def save_product(product_id: str, tenant_id: str, fields: List[Dict[str, Any]], image_data: Optional[bytes] = None, image_mime_type: Optional[str] = None):
|
||||||
"""Save or update a product for a tenant"""
|
"""Save or update a product for a tenant"""
|
||||||
|
# Normalize tenant_id to lowercase
|
||||||
|
tenant_id = tenant_id.lower()
|
||||||
|
|
||||||
with get_db() as conn:
|
with get_db() as conn:
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
|
|
||||||
@@ -336,6 +346,9 @@ def save_product(product_id: str, tenant_id: str, fields: List[Dict[str, Any]],
|
|||||||
|
|
||||||
def delete_product(product_id: str, tenant_id: str):
|
def delete_product(product_id: str, tenant_id: str):
|
||||||
"""Delete a product for a tenant"""
|
"""Delete a product for a tenant"""
|
||||||
|
# Normalize tenant_id to lowercase
|
||||||
|
tenant_id = tenant_id.lower()
|
||||||
|
|
||||||
with get_db() as conn:
|
with get_db() as conn:
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
cursor.execute('DELETE FROM products WHERE id = ? AND tenant_id = ?', (product_id, tenant_id))
|
cursor.execute('DELETE FROM products WHERE id = ? AND tenant_id = ?', (product_id, tenant_id))
|
||||||
@@ -343,6 +356,9 @@ def delete_product(product_id: str, tenant_id: str):
|
|||||||
|
|
||||||
def get_product_image(product_id: str, tenant_id: str) -> Optional[tuple[bytes, str]]:
|
def get_product_image(product_id: str, tenant_id: str) -> Optional[tuple[bytes, str]]:
|
||||||
"""Get product image data and mime type for a tenant"""
|
"""Get product image data and mime type for a tenant"""
|
||||||
|
# Normalize tenant_id to lowercase
|
||||||
|
tenant_id = tenant_id.lower()
|
||||||
|
|
||||||
with get_db() as conn:
|
with get_db() as conn:
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
cursor.execute('SELECT image_data, image_mime_type FROM products WHERE id = ? AND tenant_id = ?', (product_id, tenant_id))
|
cursor.execute('SELECT image_data, image_mime_type FROM products WHERE id = ? AND tenant_id = ?', (product_id, tenant_id))
|
||||||
@@ -420,6 +436,9 @@ def get_or_create_tenant(tenant_id: str, username: str = None, password: str = N
|
|||||||
|
|
||||||
def update_tenant_credentials(tenant_id: str, username: str, password: str):
|
def update_tenant_credentials(tenant_id: str, username: str, password: str):
|
||||||
"""Update tenant credentials"""
|
"""Update tenant credentials"""
|
||||||
|
# Normalize tenant_id to lowercase
|
||||||
|
tenant_id = tenant_id.lower()
|
||||||
|
|
||||||
with get_db() as conn:
|
with get_db() as conn:
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
cursor.execute('''
|
cursor.execute('''
|
||||||
@@ -448,6 +467,9 @@ def get_all_tenants() -> List[Dict[str, Any]]:
|
|||||||
|
|
||||||
def delete_tenant(tenant_id: str):
|
def delete_tenant(tenant_id: str):
|
||||||
"""Delete a tenant and all associated data"""
|
"""Delete a tenant and all associated data"""
|
||||||
|
# Normalize tenant_id to lowercase
|
||||||
|
tenant_id = tenant_id.lower()
|
||||||
|
|
||||||
with get_db() as conn:
|
with get_db() as conn:
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
# Due to ON DELETE CASCADE, this will also delete all products and product_fields
|
# Due to ON DELETE CASCADE, this will also delete all products and product_fields
|
||||||
@@ -475,6 +497,9 @@ def cleanup_reserved_tenants():
|
|||||||
|
|
||||||
def get_custom_ar_fields(tenant_id: str) -> List[Dict[str, Any]]:
|
def get_custom_ar_fields(tenant_id: str) -> List[Dict[str, Any]]:
|
||||||
"""Get custom AR fields for a tenant"""
|
"""Get custom AR fields for a tenant"""
|
||||||
|
# Normalize tenant_id to lowercase
|
||||||
|
tenant_id = tenant_id.lower()
|
||||||
|
|
||||||
with get_db() as conn:
|
with get_db() as conn:
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
cursor.execute('''
|
cursor.execute('''
|
||||||
@@ -499,6 +524,9 @@ def get_custom_ar_fields(tenant_id: str) -> List[Dict[str, Any]]:
|
|||||||
|
|
||||||
def save_custom_ar_field(tenant_id: str, field_data: Dict[str, Any]) -> int:
|
def save_custom_ar_field(tenant_id: str, field_data: Dict[str, Any]) -> int:
|
||||||
"""Save or update a custom AR field"""
|
"""Save or update a custom AR field"""
|
||||||
|
# Normalize tenant_id to lowercase
|
||||||
|
tenant_id = tenant_id.lower()
|
||||||
|
|
||||||
with get_db() as conn:
|
with get_db() as conn:
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
|
|
||||||
@@ -539,6 +567,9 @@ def save_custom_ar_field(tenant_id: str, field_data: Dict[str, Any]) -> int:
|
|||||||
|
|
||||||
def delete_custom_ar_field(tenant_id: str, field_id: int):
|
def delete_custom_ar_field(tenant_id: str, field_id: int):
|
||||||
"""Delete a custom AR field"""
|
"""Delete a custom AR field"""
|
||||||
|
# Normalize tenant_id to lowercase
|
||||||
|
tenant_id = tenant_id.lower()
|
||||||
|
|
||||||
with get_db() as conn:
|
with get_db() as conn:
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
cursor.execute('''
|
cursor.execute('''
|
||||||
@@ -549,17 +580,38 @@ def delete_custom_ar_field(tenant_id: str, field_id: int):
|
|||||||
|
|
||||||
def init_default_ar_fields(tenant_id: str):
|
def init_default_ar_fields(tenant_id: str):
|
||||||
"""Initialize default AR fields for a new tenant"""
|
"""Initialize default AR fields for a new tenant"""
|
||||||
|
# Normalize tenant_id to lowercase
|
||||||
|
tenant_id = tenant_id.lower()
|
||||||
|
|
||||||
default_fields = [
|
default_fields = [
|
||||||
{"fieldName": "_id", "label": "Item ID", "fieldType": "TEXT", "editable": "false", "displayOrder": 1},
|
{"fieldName": "_id", "label": "Item ID", "fieldType": "TEXT", "editable": "false", "displayOrder": 1},
|
||||||
{"fieldName": "_price", "label": "Sale Price", "fieldType": "TEXT", "editable": "true", "displayOrder": 2},
|
{"fieldName": "_price", "label": "Sale Price", "fieldType": "TEXT", "editable": "true", "displayOrder": 2},
|
||||||
{"fieldName": "_image", "label": "Image", "fieldType": "IMAGE_URI", "editable": "false", "displayOrder": 3}
|
{"fieldName": "_image", "label": "Image", "fieldType": "IMAGE_URI", "editable": "false", "displayOrder": 3}
|
||||||
]
|
]
|
||||||
|
|
||||||
for field in default_fields:
|
with get_db() as conn:
|
||||||
save_custom_ar_field(tenant_id, field)
|
cursor = conn.cursor()
|
||||||
|
for field in default_fields:
|
||||||
|
# Use INSERT OR IGNORE to avoid duplicate key errors
|
||||||
|
cursor.execute('''
|
||||||
|
INSERT OR IGNORE INTO custom_ar_fields
|
||||||
|
(tenant_id, field_name, label, field_type, editable, display_order)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?)
|
||||||
|
''', (
|
||||||
|
tenant_id,
|
||||||
|
field['fieldName'],
|
||||||
|
field['label'],
|
||||||
|
field['fieldType'],
|
||||||
|
field.get('editable', 'true'),
|
||||||
|
field.get('displayOrder', 0)
|
||||||
|
))
|
||||||
|
conn.commit()
|
||||||
|
|
||||||
def save_product_image(product_id: str, tenant_id: str, field_name: str, image_data: bytes, mime_type: str):
|
def save_product_image(product_id: str, tenant_id: str, field_name: str, image_data: bytes, mime_type: str):
|
||||||
"""Save an image for a specific field of a product"""
|
"""Save an image for a specific field of a product"""
|
||||||
|
# Normalize tenant_id to lowercase
|
||||||
|
tenant_id = tenant_id.lower()
|
||||||
|
|
||||||
with get_db() as conn:
|
with get_db() as conn:
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
|
|
||||||
@@ -579,6 +631,9 @@ def save_product_image(product_id: str, tenant_id: str, field_name: str, image_d
|
|||||||
|
|
||||||
def get_product_image_by_field(product_id: str, tenant_id: str, field_name: str) -> Optional[tuple[bytes, str]]:
|
def get_product_image_by_field(product_id: str, tenant_id: str, field_name: str) -> Optional[tuple[bytes, str]]:
|
||||||
"""Get image data for a specific field of a product"""
|
"""Get image data for a specific field of a product"""
|
||||||
|
# Normalize tenant_id to lowercase
|
||||||
|
tenant_id = tenant_id.lower()
|
||||||
|
|
||||||
with get_db() as conn:
|
with get_db() as conn:
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
cursor.execute('''
|
cursor.execute('''
|
||||||
|
|||||||
@@ -90,11 +90,14 @@
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const tenantId = document.getElementById('tenantId').value;
|
const tenantId = document.getElementById('tenantId').value;
|
||||||
if (tenantId) {
|
if (tenantId) {
|
||||||
if (reservedIds.includes(tenantId.toLowerCase())) {
|
// Convert to lowercase for consistency
|
||||||
|
const normalizedId = tenantId.toLowerCase();
|
||||||
|
if (reservedIds.includes(normalizedId)) {
|
||||||
alert(`"${tenantId}" is a reserved name and cannot be used as a tenant ID.`);
|
alert(`"${tenantId}" is a reserved name and cannot be used as a tenant ID.`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
window.location.href = '/' + tenantId + '/';
|
// Use the normalized (lowercase) ID in the URL
|
||||||
|
window.location.href = '/' + normalizedId + '/';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user