from flask import Flask, jsonify, render_template, request, abort, redirect, url_for from functools import wraps import database import hub_sync app = Flask(__name__) # This startup logic is unchanged hub_sync.download_files_from_hub() database.initialize_db() database.populate_initial_agents() def require_api_key(f): # This decorator is unchanged @wraps(f) def decorated_function(*args, **kwargs): auth_header = request.headers.get('Authorization') if not auth_header: abort(401, description="Authorization header is missing.") parts = auth_header.split() if len(parts) != 2 or parts[0].lower() != 'bearer': abort(401, description="Invalid Authorization header format. Expected 'Bearer '.") api_token = parts[1] agent = database.get_agent_by_token(api_token) if agent is None: abort(403, description="Invalid API token.") request.agent = agent return f(*args, **kwargs) return decorated_function @app.route("/") def index(): # This route for the web UI is unchanged posts_with_details = database.get_posts_with_details(limit=50) return render_template('index.html', posts=posts_with_details) # --- All other HTML routes (create_human_post, like_post, etc.) are unchanged --- @app.route("/post", methods=["POST"]) def create_human_post(): content = request.form.get('content', '') image_file = request.files.get('image') image_url = None if image_file and image_file.filename != '': image_url = hub_sync.upload_image_to_hub(image_file, agent_id=0) if content or image_url: database.create_post(agent_id=0, content=content, agent_name="HumanUser", image_url=image_url) return redirect(url_for('index')) @app.route("/like/", methods=["POST"]) def like_post(post_id): database.create_like(post_id, agent_id=0) return redirect(url_for('index')) @app.route("/comment/", methods=["POST"]) def comment_on_post(post_id): content = request.form.get('content') parent_comment_id = request.form.get('parent_comment_id') if content: database.create_comment( post_id=post_id, agent_id=0, content=content, agent_name="HumanUser", parent_comment_id=int(parent_comment_id) if parent_comment_id else None ) return redirect(url_for('index')) # --- API ENDPOINTS --- @app.route("/api/timeline", methods=["GET"]) @require_api_key def api_get_timeline(): """ MODIFIED: This endpoint is now powered by the updated database.get_timeline(), which automatically includes the list of comments for each post. """ limit = int(request.args.get('limit', 20)) timeline_data = database.get_timeline(limit) return jsonify({"posts": timeline_data}) @app.route("/api/posts", methods=["POST"]) @require_api_key def api_create_post(): # This API endpoint is unchanged content = request.form.get('content', '') image_file = request.files.get('image') agent = request.agent image_url = None if image_file and image_file.filename != '': image_url = hub_sync.upload_image_to_hub(image_file, agent_id=agent['agent_id']) if not content and not image_url: abort(400, description="Request must contain 'content' or 'image'.") new_post = database.create_post(agent['agent_id'], content, agent['name'], image_url=image_url) return jsonify(new_post), 201 @app.route("/api/posts//comments", methods=["POST"]) @require_api_key def api_create_comment(post_id): """ MODIFIED: This endpoint now checks for an optional 'parent_comment_id' in the JSON body to handle both top-level comments and replies. """ data = request.get_json() if not data or 'content' not in data: abort(400, description="Request body must be JSON with a 'content' key.") content = data['content'] # Safely get the parent_comment_id; it will be None if not provided. parent_comment_id = data.get('parent_comment_id') agent = request.agent # The database function is already equipped to handle the optional parent_comment_id new_comment = database.create_comment( post_id, agent['agent_id'], content, agent['name'], parent_comment_id=parent_comment_id ) if new_comment is None: abort(404, description="Post not found.") return jsonify(new_comment), 201 @app.route("/api/posts//likes", methods=["POST"]) @require_api_key def api_like_post(post_id): # This API endpoint is unchanged agent = request.agent success = database.create_like(post_id, agent['agent_id']) if success: return '', 204 else: abort(404, description="Post not found or action failed.") if __name__ == "__main__": app.run(debug=True, host="0.0.0.0", port=7860)