# -*- coding: utf-8 -*- import os import sqlite3 import json import requests from flask import Flask, request, jsonify, g app = Flask(__name__) @app.after_request def add_cors_headers(response): response.headers['Access-Control-Allow-Origin'] = '*' response.headers['Access-Control-Allow-Methods'] = 'GET,POST,PUT,DELETE,OPTIONS' response.headers['Access-Control-Allow-Headers'] = 'Content-Type,Authorization' return response # ---------------------------------------------------------------------- # Database utilities (unchanged) # ---------------------------------------------------------------------- DATABASE = os.path.join(os.path.dirname(__file__), "tasks.db") def get_db(): db = getattr(g, "_database", None) if db is None: db = g._database = sqlite3.connect(DATABASE, check_same_thread=False) db.row_factory = sqlite3.Row return db def init_db(): conn = sqlite3.connect(DATABASE) cursor = conn.cursor() cursor.execute( """ CREATE TABLE IF NOT EXISTS tasks ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, completed INTEGER NOT NULL DEFAULT 0 ) """ ) conn.commit() conn.close() @app.teardown_appcontext def close_connection(exception): db = getattr(g, "_database", None) if db is not None: db.close() # ---------------------------------------------------------------------- # Existing task endpoints (unchanged) # ---------------------------------------------------------------------- @app.route("/tasks", methods=["GET"]) def list_tasks(): db = get_db() cursor = db.execute("SELECT id, title, completed FROM tasks") tasks = [ {"id": row["id"], "title": row["title"], "completed": bool(row["completed"])} for row in cursor.fetchall() ] return jsonify(tasks) @app.route("/tasks", methods=["POST"]) def create_task(): data = request.get_json() title = data.get("title") if not title: return jsonify({"error": "title is required"}), 400 db = get_db() cursor = db.execute( "INSERT INTO tasks (title, completed) VALUES (?, ?)", (title, 0), ) db.commit() task_id = cursor.lastrowid return jsonify({"id": task_id, "title": title, "completed": False}), 201 @app.route("/tasks/", methods=["PUT"]) def update_task(task_id): data = request.get_json() title = data.get("title") completed = data.get("completed") if title is None and completed is None: return jsonify({"error": "nothing to update"}), 400 db = get_db() if title is not None: db.execute("UPDATE tasks SET title = ? WHERE id = ?", (title, task_id)) if completed is not None: db.execute( "UPDATE tasks SET completed = ? WHERE id = ?", (1 if completed else 0, task_id), ) db.commit() return jsonify({"id": task_id, "title": title, "completed": completed}) @app.route("/tasks/", methods=["DELETE"]) def delete_task(task_id): db = get_db() db.execute("DELETE FROM tasks WHERE id = ?", (task_id,)) db.commit() return "", 204 # ---------------------------------------------------------------------- # Health endpoints (unchanged) # ---------------------------------------------------------------------- @app.route("/", methods=["GET"]) @app.route("/test", methods=["GET"]) def index(): return "Task API is running." @app.route("/ping", methods=["GET"]) def ping(): return "pong", 200 # ---------------------------------------------------------------------- # New: Generic API connector # ---------------------------------------------------------------------- @app.route("/api/connector", methods=["GET", "POST"]) def api_connector(): """ Expects a JSON payload with the following fields: { "url": "https://example.com/endpoint", "method": "GET|POST|PUT|DELETE|PATCH", "headers": {"Authorization": "Bearer ...", ...}, # optional "params": {"q": "search"}, # optional, for query string "json": {"key": "value"} # optional, for JSON body } The endpoint forwards the request to the target API and returns the raw response (status code, headers and JSON body if possible). """ payload = request.get_json() if not payload: return jsonify({"error": "invalid json payload"}), 400 url = payload.get("url") # placeholder replacement removed method = payload.get("method", "GET").upper() headers = payload.get("headers", {}) params = payload.get("params", {}) json_body = payload.get("json", None) if not url: return jsonify({"error": "url is required"}), 400 # Optional: inject a default API key from environment if not provided default_key = os.environ.get("DEFAULT_API_KEY") if default_key and "Authorization" not in headers: headers["Authorization"] = f"Bearer {default_key}" try: response = requests.request( method=method, url=url, headers=headers, params=params, json=json_body, timeout=30 ) # Do not raise for HTTP errors; return the response as is except requests.RequestException as e: return jsonify({"error": "request failed", "details": str(e)}), 502 # Try to parse JSON, fallback to raw text try: resp_content = response.json() except ValueError: resp_content = response.text result = { "status_code": response.status_code, "headers": dict(response.headers), "content": resp_content } return jsonify(result), response.status_code # ---------------------------------------------------------------------- # Error handling (unchanged) # ---------------------------------------------------------------------- @app.errorhandler(404) def not_found(e): return jsonify({"error": "not found"}), 404 # ---------------------------------------------------------------------- # Main entry point (unchanged) # ---------------------------------------------------------------------- if __name__ == "__main__": if not os.path.isfile(DATABASE): init_db() app.run(host="0.0.0.0", port=7860)