From a93bd5d870288dca30ae3d0b606ef9381bd125f4 Mon Sep 17 00:00:00 2001 From: hzhang Date: Tue, 3 Dec 2024 00:10:47 +0000 Subject: [PATCH] resource impl --- api/__init__.py | 19 ++++++++- api/auth.py | 4 +- api/markdown.py | 13 ++++++- api/resource.py | 53 ++++++++++++++++++++++++++ app.py | 2 + contexts/RequestContext.py | 12 ++++++ contexts/__init__.py | 0 db/models/Resource.py | 16 ++++++++ logging_handlers/DatabaseLogHandler.py | 5 ++- 9 files changed, 118 insertions(+), 6 deletions(-) create mode 100644 api/resource.py create mode 100644 contexts/RequestContext.py create mode 100644 contexts/__init__.py create mode 100644 db/models/Resource.py diff --git a/api/__init__.py b/api/__init__.py index ddceb2f..9f759ed 100644 --- a/api/__init__.py +++ b/api/__init__.py @@ -1 +1,18 @@ -#api/__init__.py \ No newline at end of file +#api/__init__.py + +from functools import wraps +from flask import jsonify, session + + +def require_auth(roles=[]): + def decorator(func): + @wraps(func) + def wrapper(*args, **kwargs): + user = session.get('user') + if not user: + return jsonify({"error": "Unauthorized"}), 401 + if user.get('role') not in roles: + return jsonify({"error": "Forbidden, permission denied"}), 403 + return func(*args, **kwargs) + return wrapper + return decorator \ No newline at end of file diff --git a/api/auth.py b/api/auth.py index 8744e50..eee4307 100644 --- a/api/auth.py +++ b/api/auth.py @@ -1,6 +1,7 @@ #api/auth.py from flask import Blueprint, session, redirect, url_for, jsonify from authlib.integrations.flask_client import OAuth +from contexts.RequestContext import RequestContext import env_provider import logging logger = logging.getLogger(__name__) @@ -29,7 +30,8 @@ def authorize(): return jsonify({"message": "login successful", "user": user_info}) except Exception as e: logger.error(f"Authorization failed: {str(e)}") - return jsonify({"error": "Authorization failed"}), 401 + errno = RequestContext.get_error_id() + return jsonify({"error": f"Authorization failed - {errno}"}), 401 @auth_bp.route('/logout', methods=['GET']) def logout(): u = session.pop('user', None) diff --git a/api/markdown.py b/api/markdown.py index bc1d4b0..e74c907 100644 --- a/api/markdown.py +++ b/api/markdown.py @@ -1,5 +1,8 @@ #api/markdown.py from flask import Blueprint, request, jsonify + +from api import require_auth +from contexts.RequestContext import RequestContext from db import get_db from db.models.Markdown import Markdown import logging @@ -16,6 +19,7 @@ def get_markdown(markdown_id): return jsonify(markdown.to_dict()) @markdown_bp.route('/', methods=['POST']) +@require_auth(roles=['admin', 'creator']) def create_markdown(): data = request.json title = data.get('title') @@ -31,10 +35,12 @@ def create_markdown(): return jsonify(new_markdown.to_dict()), 201 except Exception as e: logger.error(f"failed to create markdown: {e}") + errno = RequestContext.get_error_id() db.rollback() - return jsonify({"error": "create failed"}), 500 + return jsonify({"error": f"create failed - {errno}"}), 500 @markdown_bp.route('/', methods=['PUT']) +@require_auth(roles=['admin', 'creator']) def update_markdown(markdown_id): with get_db() as db: markdown = db.query(Markdown).get(markdown_id) @@ -48,11 +54,14 @@ def update_markdown(markdown_id): return jsonify(markdown.to_dict()), 200 @markdown_bp.route('/', methods=['DELETE']) +@require_auth(roles=['admin']) def delete_markdown(markdown_id): with get_db() as db: markdown = db.query(Markdown).get(markdown_id) if markdown is None: - return jsonify({"error": "file not found"}), 404 + logger.error(f"failed to delete markdown: {markdown_id}") + errno = RequestContext.get_error_id() + return jsonify({"error": f"file not found - {errno}"}), 404 db.delete(markdown) db.commit() return jsonify({"message": "deleted"}), 200 \ No newline at end of file diff --git a/api/resource.py b/api/resource.py new file mode 100644 index 0000000..3f0a9e5 --- /dev/null +++ b/api/resource.py @@ -0,0 +1,53 @@ +#api/resource.py +from flask import Blueprint, jsonify, request + +from contexts.RequestContext import RequestContext +from db import get_db +from db.models.Resource import Resource +from api import require_auth +import logging +resource_bp = Blueprint('resource', __name__, url_prefix='/api/resource') +logger = logging.getLogger(__name__) +@resource_bp.route('/', methods=['GET']) +def get_resource(identifier): + with get_db() as db: + resource = db.query(Resource).get(identifier) + if resource is None: + logger.error(f"resource not found: {identifier}") + errno = RequestContext.get_error_id() + return jsonify({"error": f"resource not found - {errno}"}), 404 + return jsonify(resource.to_dict()), 200 + +@resource_bp.route('/', methods=['POST']) +@require_auth(roles=["admin", "creator"]) +def create_resource(): + data = request.get_json() + identifier = data.get('id') + content = data.get('content') + data_type = data.get('data_type') + if not identifier or not content or not data_type: + return jsonify({"error": "missing required fields"}), 400 + resource_entry = Resource(id=identifier, content=content, data_type=data_type) + with get_db() as db: + try: + db.add(resource_entry) + db.commit() + return jsonify(resource_entry.to_dict()), 201 + except Exception as e: + db.rollback() + logger.error(f"Failed to create resource: {e}") + errno = RequestContext.get_error_id() + return jsonify({"error": f"failed to create resource - {errno}"}), 500 + +@resource_bp.route('/', methods=['DELETE']) +@require_auth(roles=["admin"]) +def delete_resource(identifier): + with get_db() as db: + resource = db.query(Resource).get(identifier) + if not resource: + logger.error(f"resource not found: {identifier}") + errno = RequestContext.get_error_id() + return jsonify({"error": f"Resource not found - {errno}"}), 404 + db.delete(resource) + db.commit() + return jsonify({"message": "Resource deleted"}), 200 \ No newline at end of file diff --git a/app.py b/app.py index be1fd5c..c49b384 100644 --- a/app.py +++ b/app.py @@ -10,6 +10,7 @@ from api.log import logs_bp from api.markdown import markdown_bp import logging +from api.resource import resource_bp from logging_handlers.DatabaseLogHandler import DatabaseLogHandler logger = logging.getLogger(__name__) @@ -39,6 +40,7 @@ CORS(app, resources={r"/api/*": {"origins": is_allowed_origin}}) app.register_blueprint(markdown_bp) app.register_blueprint(auth_bp) app.register_blueprint(logs_bp) +app.register_blueprint(resource_bp) @app.before_request def log_request(): if request.path.startswith("/api/log"): diff --git a/contexts/RequestContext.py b/contexts/RequestContext.py new file mode 100644 index 0000000..7b6e16b --- /dev/null +++ b/contexts/RequestContext.py @@ -0,0 +1,12 @@ +import threading +class RequestContext: + _thread_local = threading.local() + + @classmethod + def set_error_id(cls, error_id): + cls._thread_local.error_id = error_id + + @classmethod + def get_error_id(cls): + return getattr(cls._thread_local, "error_id", None) + diff --git a/contexts/__init__.py b/contexts/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/db/models/Resource.py b/db/models/Resource.py new file mode 100644 index 0000000..1652d2d --- /dev/null +++ b/db/models/Resource.py @@ -0,0 +1,16 @@ +#db/models/Resource.py +from sqlalchemy import Column, Text, LargeBinary +from db.models import Base + +class Resource(Base): + __tablename__ = 'resources' + id = Column(Text, primary_key=True) + content = Column(LargeBinary, nullable=False) + data_type = Column(Text, nullable=False) + + def to_dict(self): + return{ + "id": self.id, + "data_type": self.data_type, + "content": self.content.decode('utf-8'), + } diff --git a/logging_handlers/DatabaseLogHandler.py b/logging_handlers/DatabaseLogHandler.py index 87b48ed..10bfa6f 100644 --- a/logging_handlers/DatabaseLogHandler.py +++ b/logging_handlers/DatabaseLogHandler.py @@ -1,9 +1,9 @@ #logging_handlers/DatabaseLogHandler.py import logging -import traceback from db import get_db from db.models.Log import Log +from contexts.RequestContext import RequestContext class DatabaseLogHandler(logging.Handler): def __init__(self, application="backend"): super().__init__() @@ -18,5 +18,6 @@ class DatabaseLogHandler(logging.Handler): with get_db() as db: db.add(log_entry) db.commit() + RequestContext.set_error_id(log_entry.id) except Exception: - print(f"Failed to log to database: {traceback.format_exc()}") \ No newline at end of file + print(f"failed to log") \ No newline at end of file