From ac07f5b7c849dde96aed059c66d66677d566354c Mon Sep 17 00:00:00 2001 From: Matt Hills Date: Thu, 19 Jun 2025 14:35:13 -0400 Subject: [PATCH] ability to delete a tenant --- src/app.py | 17 ++++++++++- src/database.py | 42 +++++++++++++++++++++++++-- src/templates/tenant_selection.html | 45 ++++++++++++++++++++++++----- 3 files changed, 93 insertions(+), 11 deletions(-) diff --git a/src/app.py b/src/app.py index a154996..b172660 100644 --- a/src/app.py +++ b/src/app.py @@ -30,10 +30,20 @@ def index(): tenants = database.get_all_tenants() return render_template('tenant_selection.html', tenants=tenants) +@app.route('/tenant//delete', methods=['POST']) +def delete_tenant(tenant_id): + """Delete a tenant and all its data""" + database.delete_tenant(tenant_id) + flash(f'Tenant "{tenant_id}" has been deleted successfully.', 'success') + return redirect('/') + @app.route('//') def tenant_index(tenant_id): # Auto-create tenant if it doesn't exist tenant = database.get_or_create_tenant(tenant_id) + if tenant is None: + # Reserved tenant ID or invalid + return jsonify({"error": f"'{tenant_id}' is a reserved name and cannot be used as a tenant ID"}), 404 return render_template('index.html', tenant=tenant) def check_basic_auth(auth_header, tenant_id): @@ -48,7 +58,7 @@ def check_basic_auth(auth_header, tenant_id): # Get tenant credentials from database tenant = database.get_or_create_tenant(tenant_id) - if username == tenant['username'] and password == tenant['password']: + if tenant and username == tenant['username'] and password == tenant['password']: return True except Exception: pass @@ -228,4 +238,9 @@ if __name__ == '__main__': shutil.move(products_file, products_file + '.migrated') print("Migration complete.") + # Clean up any accidentally created reserved tenants + deleted_count = database.cleanup_reserved_tenants() + if deleted_count > 0: + print(f"Cleaned up {deleted_count} reserved tenant(s)") + app.run(port=5555, host="0.0.0.0", debug=True) diff --git a/src/database.py b/src/database.py index a759c82..81f530f 100644 --- a/src/database.py +++ b/src/database.py @@ -7,6 +7,13 @@ import base64 DATABASE_PATH = os.path.join(os.path.dirname(__file__), 'data', 'products.db') +# Reserved tenant IDs that cannot be used +RESERVED_TENANT_IDS = { + 'admin', 'api', 'login', 'logout', 'arcontentfields', 'arinfo', + 'images', 'barcodes', 'static', 'assets', 'js', 'css', 'tenant', + 'auth', 'oauth', 'callback', 'webhook', 'health', 'status' +} + @contextmanager def get_db(): """Context manager for database connections""" @@ -303,8 +310,12 @@ def get_product_image(product_id: str, tenant_id: str) -> Optional[tuple[bytes, return row['image_data'], row['image_mime_type'] return None -def get_or_create_tenant(tenant_id: str, username: str = None, password: str = None) -> Dict[str, Any]: +def get_or_create_tenant(tenant_id: str, username: str = None, password: str = None) -> Optional[Dict[str, Any]]: """Get or create a tenant""" + # Check if tenant_id is reserved + if tenant_id.lower() in RESERVED_TENANT_IDS: + return None + with get_db() as conn: cursor = conn.cursor() @@ -362,4 +373,31 @@ def get_all_tenants() -> List[Dict[str, Any]]: 'created_at': row['created_at'] }) - return tenants \ No newline at end of file + return tenants + +def delete_tenant(tenant_id: str): + """Delete a tenant and all associated data""" + with get_db() as conn: + cursor = conn.cursor() + # Due to ON DELETE CASCADE, this will also delete all products and product_fields + cursor.execute('DELETE FROM tenants WHERE id = ?', (tenant_id,)) + conn.commit() + +def cleanup_reserved_tenants(): + """Remove any tenants that were accidentally created with reserved IDs""" + with get_db() as conn: + cursor = conn.cursor() + # Get all tenants + cursor.execute('SELECT id FROM tenants') + tenants = cursor.fetchall() + + deleted_count = 0 + for tenant in tenants: + tenant_id = tenant['id'] + if tenant_id.lower() in RESERVED_TENANT_IDS: + cursor.execute('DELETE FROM tenants WHERE id = ?', (tenant_id,)) + deleted_count += 1 + print(f"Deleted reserved tenant: {tenant_id}") + + conn.commit() + return deleted_count \ No newline at end of file diff --git a/src/templates/tenant_selection.html b/src/templates/tenant_selection.html index 1cb4c5f..427672a 100644 --- a/src/templates/tenant_selection.html +++ b/src/templates/tenant_selection.html @@ -10,6 +10,17 @@

KCAP Demo Server - Multi-Tenant

+ {% with messages = get_flashed_messages(with_categories=true) %} + {% if messages %} + {% for category, message in messages %} + + {% endfor %} + {% endif %} + {% endwith %} +
+ \ No newline at end of file