Move http endpoints into seperate route

This commit is contained in:
Peter Stockings
2025-07-21 22:05:58 +10:00
parent 917189b3d9
commit 2ec44252bb
12 changed files with 552 additions and 304 deletions

342
app.py
View File

@@ -13,6 +13,7 @@ from dotenv import load_dotenv
from routes.timer import timer
from routes.test import test
from routes.home import home
from routes.http import http
from flask_apscheduler import APScheduler
import asyncio
import aiohttp
@@ -37,6 +38,7 @@ init_app(app)
app.register_blueprint(timer, url_prefix='/timer')
app.register_blueprint(test, url_prefix='/test')
app.register_blueprint(home, url_prefix='/home')
app.register_blueprint(http, url_prefix='/dashboard/http_functions')
class User(UserMixin):
def __init__(self, id, username, password_hash, created_at):
@@ -126,140 +128,6 @@ def dashboard():
http_functions = create_http_functions_view_model(http_functions)
return render_template("dashboard/http_functions/overview.html", http_functions=http_functions)
@ app.route("/dashboard/http_functions", methods=["GET"])
@login_required
def dashboard_http_functions():
user_id = current_user.id
http_functions = db.get_http_functions_for_user(user_id)
http_functions = create_http_functions_view_model(http_functions)
if htmx:
return render_block(app.jinja_env, "dashboard/http_functions/overview.html", "page", http_functions=http_functions)
return render_template("dashboard/http_functions/overview.html", http_functions=http_functions)
@ app.route("/dashboard/http_functions/add_form", methods=["GET"])
@login_required
def get_http_function_add_form():
user_id = current_user.id
if htmx:
return render_block(app.jinja_env, 'dashboard/http_functions/new.html', 'page', user_id=user_id, name=DEFAULT_FUNCTION_NAME, script=DEFAULT_SCRIPT, environment_info=DEFAULT_ENVIRONMENT, is_public=False, log_request=True, log_response=False)
return render_template("dashboard/http_functions/new.html", user_id=user_id, name=DEFAULT_FUNCTION_NAME, script=DEFAULT_SCRIPT, environment_info=DEFAULT_ENVIRONMENT, is_public=False, log_request=True, log_response=False)
@ app.route("/dashboard/http_functions/create", methods=["POST"])
@login_required
def create_http_function():
try:
user_id = current_user.id
name = request.form.get('name')
script_content = request.form.get('script_content')
environment_info = request.form.get('environment_info')
is_public = request.form.get('is_public')
log_request = request.form.get('log_request')
log_response = request.form.get('log_response')
db.create_new_http_function(user_id, name, script_content, environment_info, is_public, log_request, log_response)
http_functions = db.get_http_functions_for_user(user_id)
http_functions = create_http_functions_view_model(http_functions)
return render_block(app.jinja_env, "dashboard/http_functions/overview.html", "page", http_functions=http_functions), 200, {"HX-Push-Url": url_for('dashboard_http_functions')}
except Exception as e:
print(e)
return { "status": "error", "message": str(e) }
@ app.route("/dashboard/http_functions/<int:function_id>/edit", methods=["POST"])
@login_required
def edit_http_function(function_id):
try:
user_id = current_user.id
name = request.json.get('name')
script_content = request.json.get('script_content')
environment_info = request.json.get('environment_info')
is_public = request.json.get('is_public')
log_request = request.json.get('log_request')
log_response = request.json.get('log_response')
updated_version = db.edit_http_function(user_id, function_id, name, script_content, environment_info, is_public, log_request, log_response)
return { "status": "success", "message": f'{name} updated' }
except Exception as e:
print(e)
return { "status": "error", "message": str(e) }
@ app.route("/dashboard/http_functions/<int:function_id>/delete", methods=["DELETE"])
@login_required
def delete_http_function(function_id):
try:
user_id = current_user.id
db.delete_http_function(user_id, function_id)
http_functions = db.get_http_functions_for_user(user_id)
http_functions = create_http_functions_view_model(http_functions)
return render_block(app.jinja_env, "dashboard/http_functions/overview.html", "page", http_functions=http_functions), 200, {"HX-Push-Url": url_for('dashboard_http_functions')}
except Exception as e:
return jsonify({"status": 'error', "message": str(e)}), 500
@ app.route("/dashboard/http_functions/<int:function_id>/logs", methods=["GET"])
@login_required
def get_http_function_logs(function_id):
user_id = current_user.id
http_function = db.get_http_function_by_id(user_id, function_id)
if not http_function:
return jsonify({'error': 'Function not found'}), 404
name = http_function['name']
http_function_invocations = db.get_http_function_invocations(function_id)
if htmx:
return render_block(app.jinja_env, 'dashboard/http_functions/logs.html', 'page', user_id=user_id, function_id=function_id, name=name, http_function_invocations=http_function_invocations)
return render_template("dashboard/http_functions/logs.html", user_id=user_id, name=name, function_id=function_id, http_function_invocations=http_function_invocations)
@ app.route("/dashboard/http_functions/<int:function_id>/client", methods=["GET"])
@login_required
def client(function_id):
user_id = current_user.id
http_function = db.get_http_function_by_id(user_id, function_id)
if not http_function:
return jsonify({'error': 'Function not found'}), 404
http_function = create_http_function_view_model(http_function)
if htmx:
return render_block(app.jinja_env, 'dashboard/http_functions/client.html', 'page', function_id=function_id, **http_function)
return render_template("dashboard/http_functions/client.html", function_id=function_id, **http_function)
@app.route("/dashboard/http_functions/<int:function_id>/history", methods=["GET"])
@login_required
def get_http_function_history(function_id):
user_id = current_user.id
http_function = db.get_http_function_by_id(user_id, function_id)
if not http_function:
return jsonify({'error': 'Function not found'}), 404
name = http_function['name']
version_number = http_function['version_number']
original_script = http_function['script_content'] if version_number == 1 else None
http_function_history = []
if version_number > 1:
raw_history = db.get_http_function_history(function_id)
for i in range(len(raw_history) - 1):
pre_version = raw_history[i + 1]
post_version = raw_history[i]
http_function_history.append({
'pre': pre_version['script_content'],
'post': post_version['script_content'],
'version_id': post_version['version_id'],
'version_number': post_version['version_number'],
'updated_at': post_version['updated_at']
})
if raw_history:
original_script = raw_history[-1]['script_content']
if htmx:
return render_block(app.jinja_env, 'dashboard/http_functions/history.html', 'page', user_id=user_id, function_id=function_id, name=name, http_function=http_function, http_function_history=http_function_history, original_script=original_script)
return render_template("dashboard/http_functions/history.html", user_id=user_id, name=name, function_id=function_id, http_function=http_function, http_function_history=http_function_history, original_script=original_script)
def _generate_script_from_natural_language(natural_query):
"""Generates a Javascript function from natural language using Gemini REST API."""
@@ -275,33 +143,68 @@ def _generate_script_from_natural_language(natural_query):
prompt = f"""
You are an expert Javascript developer. Your task is to write a complete, production-ready Javascript async function based on the user's request.
**Function Signature:**
Your function MUST have the following signature: `async (req, environment) => {{ ... }}`
- `req`: An object containing details about the incoming HTTP request (e.g., `req.method`, `req.headers`, `req.body`, `req.query`).
- `environment`: A mutable JSON object that persists across executions. You can read and write to it to maintain state.
**Environment & Constraints:**
- The function will be executed in a simple, sandboxed Javascript environment.
- **DO NOT** use `require()`, `import`, or any other module loading system. The environment does not support it.
- **CRITICAL**: ALL helper functions or variables MUST be defined *inside* the main `async` function. Do not define anything in the global scope.
- **DO NOT** use `require()`, `import`, or any other module loading system.
- **DO NOT** access the file system (`fs` module) or make network requests.
- The function must be a single `async` arrow function.
- You have access to a persistent JSON object called `environment`. You can read from and write to it. For example: `environment.my_variable = 'hello'`.
- You also have access to a `console.log()` function for logging.
- You have access to a `console.log()` function for logging.
**Input:**
The function receives one argument: `req`, an object containing details about the incoming HTTP request. It has properties like `req.method`, `req.headers`, `req.body`, `req.query`, etc.
**Output:**
You must use one of the following helper functions to return a response:
**Response Helpers:**
You must use one of the following functions to return a response:
- `HtmlResponse(body)`: Returns an HTML response.
- `JsonResponse(body)`: Returns a JSON response. The `body` will be automatically stringified.
- `JsonResponse(body)`: Returns a JSON response.
- `TextResponse(body)`: Returns a plain text response.
- `RedirectResponse(url)`: Redirects the user to a different URL.
- `RedirectResponse(url)`: Redirects the user.
**Example:**
**Complex Example (Tic-Tac-Toe):**
```javascript
async (req) => {{
if (!environment.counter) {{
environment.counter = 0;
async (req, environment) => {{
// Helper function defined INSIDE the main function
function checkWinner(board) {{
const winConditions = [
[0, 1, 2], [3, 4, 5], [6, 7, 8],
[0, 3, 6], [1, 4, 7], [2, 5, 8],
[0, 4, 8], [2, 4, 6]
];
for (const condition of winConditions) {{
const [a, b, c] = condition;
if (board[a] && board[a] === board[b] && board[a] === board[c]) {{
return board[a];
}}
}}
return null;
}}
environment.counter++;
console.log("The counter is now " + environment.counter);
return HtmlResponse(`<h1>Counter: ${{environment.counter}}</h1>`);
if (!environment.board) {{
environment.board = Array(9).fill("");
environment.turn = "X";
environment.winner = null;
}}
if (req.method === "POST" && req.json && req.json.move !== undefined) {{
const move = parseInt(req.json.move);
if (environment.board[move] === "" && !environment.winner) {{
environment.board[move] = environment.turn;
environment.turn = environment.turn === "X" ? "O" : "X";
environment.winner = checkWinner(environment.board);
}}
}}
const boardHTML = environment.board.map((cell, index) => `<button hx-post="/f/${{req.path.split('/')[2]}}/${{req.path.split('/')[3]}}" hx-target="body" hx-swap="innerHTML" name="move" value="${{index}}">${{cell || '&nbsp;'}}</button>`).join("");
const message = environment.winner ? `Player ${{environment.winner}} wins!` : `Turn: ${{environment.turn}}`;
return HtmlResponse(`
<html>
<head><title>Tic-Tac-Toe</title><script src="https://unpkg.com/htmx.org@1.9.9"></script></head>
<body><h1>${{message}}</h1><div>${{boardHTML}}</div></body>
</html>
`);
}}
```
@@ -414,7 +317,18 @@ def execute_http_function(user_id, function):
return jsonify({'error': 'Function not found'}), 404
code = http_function['script_content']
environment = http_function['environment_info']
environment_info = http_function['environment_info']
# Ensure environment is a dictionary
if isinstance(environment_info, str) and environment_info:
try:
environment = json.loads(environment_info)
except json.JSONDecodeError:
environment = {}
elif isinstance(environment_info, dict):
environment = environment_info
else:
environment = {}
is_public = http_function['is_public']
log_request = http_function['log_request']
log_response = http_function['log_response']
@@ -538,129 +452,7 @@ def logout():
logout_user()
return redirect(url_for('home'))
@app.route("/http_function_editor/<int:function_id>", methods=["GET"])
@login_required
def http_function_editor(function_id):
user_id = current_user.id
http_function = db.get_http_function_by_id(user_id, function_id)
if not http_function:
return jsonify({'error': 'Function not found'}), 404
# Create a view model with all necessary data for the editor
editor_data = {
'id': http_function['id'],
'name': http_function['name'],
'script_content': http_function['script_content'],
'environment_info': json.dumps(http_function['environment_info'], indent=2),
'is_public': http_function['is_public'],
'log_request': http_function['log_request'],
'log_response': http_function['log_response'],
'version_number': http_function['version_number'],
'user_id': user_id,
'function_id': function_id,
# Add new URLs for navigation
'cancel_url': url_for('dashboard_http_functions'),
'edit_url': url_for('http_function_editor', function_id=function_id),
}
if htmx:
return render_block(app.jinja_env, "dashboard/http_functions/editor.html", "page", **editor_data)
return render_template("dashboard/http_functions/editor.html", **editor_data)
@app.route("/api/http_functions", methods=["POST"])
@login_required
def api_create_http_function():
try:
user_id = current_user.id
data = request.get_json()
name = data.get('name')
script_content = data.get('script_content')
environment_info = data.get('environment_info')
is_public = data.get('is_public')
log_request = data.get('log_request')
log_response = data.get('log_response')
# Check if function with same name already exists for this user
existing_function = db.get_http_function(user_id, name)
if existing_function:
return jsonify({
"status": "error",
"message": f"A function with the name '{name}' already exists"
}), 400
http_function = db.create_new_http_function(
user_id,
name,
script_content,
environment_info,
is_public,
log_request,
log_response
)
return jsonify({
"status": "success",
"message": f'{name} created',
"function": http_function
})
except Exception as e:
return jsonify({
"status": "error",
"message": str(e)
}), 400
@app.route("/api/http_functions/<int:function_id>", methods=["POST"])
@login_required
def api_update_http_function(function_id):
try:
user_id = current_user.id
data = request.get_json()
name = data.get('name')
script_content = data.get('script_content')
environment_info = data.get('environment_info')
is_public = data.get('is_public')
log_request = data.get('log_request')
log_response = data.get('log_response')
updated_function = db.edit_http_function(
user_id,
function_id,
name,
script_content,
environment_info,
is_public,
log_request,
log_response
)
return jsonify({
"status": "success",
"message": f'{name} updated',
"function": updated_function
})
except Exception as e:
return jsonify({
"status": "error",
"message": str(e)
}), 400
@app.route("/api/http_functions/<int:function_id>", methods=["DELETE"])
@login_required
def api_delete_http_function(function_id):
try:
user_id = current_user.id
db.delete_http_function(user_id, function_id)
return jsonify({
"status": "success",
"message": "Function deleted successfully"
})
except Exception as e:
return jsonify({
"status": "error",
"message": str(e)
}), 400
@login_manager.user_loader
def load_user(user_id):