From 290b141d32fc2e9761e5039a50a70406a6891362 Mon Sep 17 00:00:00 2001 From: Peter Stockings Date: Mon, 1 Dec 2025 10:04:19 +1100 Subject: [PATCH] Add functionality to query DB through settings --- routes/settings.py | 80 ++++++++ .../dashboard/settings/database_schema.html | 176 +++++++++++++++++- 2 files changed, 253 insertions(+), 3 deletions(-) diff --git a/routes/settings.py b/routes/settings.py index 4a24e42..b72b434 100644 --- a/routes/settings.py +++ b/routes/settings.py @@ -205,3 +205,83 @@ def get_database_schema(): }) return schema_data + +@settings.route("/execute_query", methods=["POST"]) +@login_required +def execute_query(): + """Execute a user-scoped SQL query""" + query = request.json.get('query', '').strip() + + if not query: + return {"error": "No query provided"}, 400 + + # Basic validation - must be SELECT only + query_upper = query.upper() + if not query_upper.startswith('SELECT'): + return {"error": "Only SELECT queries are allowed"}, 400 + + # Check for dangerous keywords + dangerous_keywords = ['DROP', 'DELETE', 'INSERT', 'UPDATE', 'ALTER', 'CREATE', 'TRUNCATE', 'GRANT', 'REVOKE'] + for keyword in dangerous_keywords: + if keyword in query_upper: + return {"error": f"Keyword '{keyword}' is not allowed"}, 400 + + user_id = current_user.id + + # List of tables that have user_id column + user_scoped_tables = [ + 'http_functions', 'timer_functions', 'shared_environments', + 'api_keys', 'http_function_invocations', 'timer_function_invocations' + ] + + # Automatically add user_id filter if querying user-scoped tables + modified_query = query + for table in user_scoped_tables: + if table in query.lower(): + # Add WHERE clause if not present + if 'WHERE' not in query_upper: + modified_query = f"{query} WHERE {table}.user_id = {user_id}" + # Append to existing WHERE clause + elif f'{table}.user_id' not in query.lower() and 'user_id' not in query.lower(): + modified_query = f"{query} AND {table}.user_id = {user_id}" + break + + # Limit results to prevent massive queries + if 'LIMIT' not in query_upper: + modified_query = f"{modified_query} LIMIT 100" + + try: + results = db.execute(modified_query) + + if not results: + return { + "columns": [], + "rows": [], + "row_count": 0, + "message": "Query executed successfully, but returned no results." + } + + # Convert results to JSON-serializable format + columns = list(results[0].keys()) if results else [] + rows = [] + + for row in results: + row_data = [] + for col in columns: + value = row[col] + # Convert datetime to string + if hasattr(value, 'isoformat'): + value = value.isoformat() + row_data.append(value) + rows.append(row_data) + + return { + "columns": columns, + "rows": rows, + "row_count": len(rows), + "message": f"Query executed successfully. {len(rows)} row(s) returned.", + "query_executed": modified_query + } + + except Exception as e: + return {"error": f"Query execution failed: {str(e)}"}, 500 diff --git a/templates/dashboard/settings/database_schema.html b/templates/dashboard/settings/database_schema.html index a50b928..2b36c9c 100644 --- a/templates/dashboard/settings/database_schema.html +++ b/templates/dashboard/settings/database_schema.html @@ -23,8 +23,7 @@

Database Schema

-

Visual representation of your database structure with tables and - relationships.

+

Explore your database structure and run queries on your data.

@@ -42,13 +41,60 @@ erDiagram {% endfor %} {% for table in schema_info %} {% for fk in table.foreign_keys %} - {{ table.table_name|upper }} }|--|| {{ fk.foreign_table_name|upper }} : "references" + {{ table.table_name|upper }} }|--|| {{ fk.foreign_table_name|upper }} : "{{ fk.column_name }}" {% endfor %} {% endfor %} + +
+

SQL Query Explorer

+

+ Run SELECT queries on your data. Queries are automatically scoped to your user account for security. +

+ +
+ +
+
+
+ +
+ + +
+ + + + +
+

Tables Overview

@@ -115,6 +161,130 @@ erDiagram
+ + + + + + +