From 248e597c1f4ac8fcce4eb83ab3f3b6039ffe80d1 Mon Sep 17 00:00:00 2001 From: Akashdeep Dhar Date: Wed, 17 Jul 2024 12:57:48 +0530 Subject: [PATCH] Attempt fixing the merged endpoints codebase Co-authored-by: Akashdeep Dhar Co-authored-by: Mehmet Baran Geylani --- .../endpoints/apikey.py | 73 ---------- .../endpoints/service.py | 130 +++++++++--------- webhook_to_fedora_messaging/endpoints/user.py | 40 +++--- webhook_to_fedora_messaging/endpoints/util.py | 50 +++---- webhook_to_fedora_messaging/main.py | 15 +- webhook_to_fedora_messaging/test.py | 27 ++++ 6 files changed, 140 insertions(+), 195 deletions(-) delete mode 100644 webhook_to_fedora_messaging/endpoints/apikey.py create mode 100644 webhook_to_fedora_messaging/test.py diff --git a/webhook_to_fedora_messaging/endpoints/apikey.py b/webhook_to_fedora_messaging/endpoints/apikey.py deleted file mode 100644 index e767471..0000000 --- a/webhook_to_fedora_messaging/endpoints/apikey.py +++ /dev/null @@ -1,73 +0,0 @@ -from flask import Blueprint, Flask, request, Response, Request -from ..database import db -from ..models.apikey import APIKey -from ..models.user import User -from datetime import datetime -from sqlalchemy_helpers import get_or_create -from .util import not_found, success, bad_request, created, conflict, validate_request - -app = Flask(__name__) -apikey_endpoint = Blueprint("apikey_endpoint", __name__) - - -@apikey_endpoint.route("/apikey", methods=["POST"]) -@validate_request -def create_apikey(): - """ - Used for creating a new service by sending a post request to /service/ path. - """ - - session = db.Session() - body = request.json - user = session.query(User).filter(User.username == body['username']).first() - if user is None: - return not_found() - # Can be different parsing here. - valid_till = datetime.strptime(body['valid_till'], "%Y-%m-%d") - apikey, is_created = get_or_create(session, APIKey, user_id=user.id, name=body['name'], expiry_date=valid_till) - - if not is_created: - return conflict({'message': 'Key Already Exists'}) - else: - return created({'message': 'Created', 'uuid': apikey.id, 'code': apikey.token}) - - -@apikey_endpoint.route("/apikey/search", methods=["GET"]) -def list_apikey(): - - session = db.Session() - user = session.query(User).filter(User.username.like(request.json['username'])).first() - if user is None: - return not_found() - - apikeys = session.query(APIKey).filter(APIKey.user_id == user.id).all() - - return success({'apikey_list': apikeys}) - - -@apikey_endpoint.route("/apikey", methods=["GET"]) -@validate_request(['apikey_uuid']) -def lookup_apikey(): - session = db.Session() - apikey = session.query(APIKey).filter(APIKey.id == request.json['apikey_uuid']).first() - - if apikey is None: - return not_found() - else: - valid_till = datetime.strftime(apikey.expiry_date, "%Y-%m-%d") - return success({'uuid': apikey.id, 'name': apikey.name, 'valid_till': valid_till, 'valid': not apikey.disabled}) - - -@apikey_endpoint.route("/apikey/revoke", methods=["PUT"]) -@validate_request(['apikey_uuid', 'username']) -def revoke_service(): - session = db.Session() - user = session.query(User).filter(User.username == request.json['username']).first() - apikey = session.query(APIKey).filter(APIKey.user_id == user.id and APIKey.id == request.json['apikey_uuid']).first() - if apikey is None: - return not_found() - else: - apikey.disabled = True - session.commit() - return success({'uuid': apikey.id, 'is_valid': not apikey.disabled}) - \ No newline at end of file diff --git a/webhook_to_fedora_messaging/endpoints/service.py b/webhook_to_fedora_messaging/endpoints/service.py index 190efa5..06bfbff 100644 --- a/webhook_to_fedora_messaging/endpoints/service.py +++ b/webhook_to_fedora_messaging/endpoints/service.py @@ -1,58 +1,55 @@ -from flask import Blueprint, Flask, request, Response, Request +from flask import request, abort, Blueprint from ..database import db from ..models.service import Service from ..models.user import User from sqlalchemy_helpers import get_or_create -from .util import not_found, success, bad_request, created, conflict, validate_request, exclude_from_val, unprocessable_entity +from .util import validate_request +import uuid -app = Flask(__name__) -service_endpoint = Blueprint("service_endpoint", __name__) +service_endpoint = Blueprint("service_endpoint", __name__, url_prefix="/service") -@service_endpoint.route("/service", methods=["POST"]) +@service_endpoint.route("/", methods=["POST"]) +@validate_request(['username', 'type', 'desc', 'name']) def create_service(): """ Used for creating a new service by sending a post request to /service/ path. - """ - if not validate_request(request.json, fields=['username', 'type', 'desc', 'name']): - return unprocessable_entity() - - session = db.Session() - body = request.json - - - user = session.query(User).filter(User.username == body['username']).first() + """ + user = db.session.query(User).filter(User.username == request.json['username']).first() if user is None: - return not_found() - - service, is_created = get_or_create(session, Service, name=body['name'], type=body['type'], desc=body['desc'], user_id=user.id) + return {'message': 'Not Found'}, 404 + service, is_created = get_or_create(db.session, Service, name=request.json['name'], type=request.json['type'], desc=request.json['desc'], user_id=user.id) if not is_created: - return conflict({'message': 'Service Already Exists'}) + return abort(409, {'message': 'Service Already Exists'}) else: - return created({'message': 'Created', 'uuid': service.id}) + db.session.commit() + return {'message': 'Created', 'uuid': service.id, 'token': service.token}, 201 - -@service_endpoint.route("/service/search", methods=["GET"]) + +@service_endpoint.route("/search", methods=["GET"]) +@validate_request def list_services(): """ Used for listing all services belong to a user by sending a get request to /service/search path - """ - if not validate_request(request): - return unprocessable_entity() - - session = db.Session() - user = session.query(User).filter(User.username.like(request.json['username'])).first() + """ + user = db.session.query(User).filter(User.username.like(request.json['username'])).first() if user is None: - return not_found() - - services = session.query(Service).filter(Service.user_id == user.id).all() - - return success({'service_list': services}) - - -@service_endpoint.route("/service", methods=["GET"]) + return {'message': 'Not Found'}, 404 + services = db.session.query(Service).filter(Service.user_id == user.id).all() + return {'service_list': [{ + 'id': service.id, + 'user_id': service.user_id, + 'name': service.name, + 'type': service.type, + 'desc': service.desc, + 'disabled': service.disabled + } for service in services]}, 200 + + +@service_endpoint.route("/", methods=["GET"]) +@validate_request(['service_uuid']) def lookup_service(): """ Used for retrieving a service by it's uuid by sending a get request @@ -61,19 +58,15 @@ def lookup_service(): Request Body: service_uuid: Service UUID """ - if not validate_request(request, ['service_uuid']): - return unprocessable_entity() - - session = db.Session() - service = session.query(Service).filter(Service.id == request.json['service_uuid']).first() - + service = db.session.query(Service).filter(Service.id == request.json['service_uuid']).first() if service is None: - return not_found() + return {'message': 'Not Found'}, 404 else: - return success({'uuid': service.id, 'name': service.name, 'type': service.type, 'desc': service.desc}) + return {'uuid': service.id, 'name': service.name, 'type': service.type, 'desc': service.desc}, 200 -@service_endpoint.route("/service/revoke", methods=["PUT"]) +@service_endpoint.route("/revoke", methods=["PUT"]) +@validate_request(['username', 'service_uuid']) def revoke_service(): """ Used for revoking a service by sending a PUT request to /service/revoke path. @@ -82,25 +75,22 @@ def revoke_service(): service_uuid: Service UUID username: Username of the user that servicce belongs to. """ - if not validate_request(request, fields=['username', 'service_uuid']): - return unprocessable_entity() - - session = db.Session() - user = session.query(User).filter(User.username == request.json['username']).first() + user = db.session.query(User).filter(User.username == request.json['username']).first() if user is None: - return not_found() + return {'message': 'Not Found'}, 404 - service = session.query(Service).filter(Service.user_id == user.id).filter(Service.id == request.json['service_uuid']).first() + service = db.session.query(Service).filter(Service.user_id == user.id).filter(Service.id == request.json['service_uuid']).first() if service is None: - return not_found() - + return {'message': 'Not Found'}, 404 + service.disabled = True - session.commit() + db.session.commit() - return success({'uuid': service.id, 'is_valid': not service.disabled}) + return {'uuid': service.id, 'is_valid': not service.disabled}, 200 -@service_endpoint.route("/service", methods=["PUT"]) +@service_endpoint.route("/", methods=["PUT"]) +@validate_request(['service_uuid']) def update_service(): """ Used for updating a service by sending a PUT request to /service path. @@ -111,15 +101,23 @@ def update_service(): mesg_body: Updated message body (optional) """ - if not validate_request(request, fields=['uuid', 'name', 'mesg_body']): - return unprocessable_entity() - - session = db.Session() - service = session.query(Service).filter(Service.id == request.json['uuid']).first() + service = db.session.query(Service).filter(Service.id == request.json['service_uuid']).first() if service is None: - return not_found() + return {'message': 'Not Found'}, 404 - service.name = request.json['name'] if request.json['name'] is not None and request.json['name'] != "" else service.name - service.desc = request.json['mesg_body'] if request.json['mesg_body'] is not None and request.json['mesg_body'] != "" else service.desc - session.commit() - return success({'uuid': service.id, 'name': service.name, 'mesg_body': service.desc, 'is_valid': not service.disabled}) + service.name = request.json['name'] if "name" in request.json and request.json['name'] != "" else service.name + service.desc = request.json['mesg_body'] if "mesg_body" in request.json and request.json['mesg_body'] != "" else service.desc + db.session.commit() + return {'uuid': service.id, 'name': service.name, 'mesg_body': service.desc, 'is_valid': not service.disabled}, 200 + + +@service_endpoint.route("/token", methods=['POST']) +@validate_request(['service_uuid']) +def refresh_token(): + service = db.session.query(Service).filter(Service.id == request.json['service_uuid']).first() + if service is None: + return {'message': 'Not Found'}, 404 + + service.token = uuid.uuid4().hex + db.session.commit() + return {'uuid': service.id, 'name': service.name, 'mesg_body': service.desc, 'is_valid': not service.disabled, 'token': service.token}, 200 diff --git a/webhook_to_fedora_messaging/endpoints/user.py b/webhook_to_fedora_messaging/endpoints/user.py index 10f10ac..8e92600 100644 --- a/webhook_to_fedora_messaging/endpoints/user.py +++ b/webhook_to_fedora_messaging/endpoints/user.py @@ -1,15 +1,14 @@ -from flask import Blueprint, Flask, request, Response, Request +from flask import request, Blueprint from ..database import db from ..models.user import User from sqlalchemy_helpers import get_or_create -from .util import success, bad_request, conflict, created, not_found, validate_request, unprocessable_entity +from .util import validate_request -app = Flask(__name__) -user_endpoint = Blueprint("user_endpoint", __name__) +user_endpoint = Blueprint("user_endpoint", __name__, url_prefix="/user") -@user_endpoint.route("/user", methods=["POST"]) +@user_endpoint.route("/", methods=["POST"]) @validate_request def create_user(): """Used for creating a new user by sending a post request to /user/ path. @@ -18,15 +17,15 @@ def create_user(): username: Username of the user """ - session = db.Session() - user, is_created = get_or_create(session, User, username=request.json['username']) + user, is_created = get_or_create(db.session, User, username=request.json['username']) + db.session.commit() if not is_created: - return conflict({'message': 'User Already Exists'}) + return {'message': 'User Already Exists'}, 409 else: - return created({'message': 'Created', 'uuid': user.id}) + return {'uuid': user.id}, 201 - -@user_endpoint.route("/user/search", methods=["GET"]) + +@user_endpoint.route("/search", methods=["GET"]) @validate_request def get_user(): """Used for retrieving a user by sending a get request to /user/search path. @@ -35,15 +34,14 @@ def get_user(): username: Username of the user """ - session = db.Session() - users = session.query(User).filter(User.username.like(request.json['username'])).all() + users = db.session.query(User).filter(User.username.like(request.json['username'])).all() if users is None or users == []: - return not_found() + return {'message': 'Not Found'}, 404 else: - return success({'user_list': users}) - + return {'user_list': [{'uuid': user.id, 'username': user.username} for user in users]}, 200 -@user_endpoint.route("/user", methods=["GET"]) + +@user_endpoint.route("/", methods=["GET"]) @validate_request def lookup_user(): """Used for searching a user by sending a get request to /user/ path. @@ -51,10 +49,8 @@ def lookup_user(): Request Body: username: Username of the user """ - session = db.Session() - - user = session.query(User).filter(User.username == request.json['username']).first() + user = db.session.query(User).filter(User.username == request.json['username']).first() if user is None: - return not_found() + return {'message': 'Not Found'}, 404 else: - return success({'uuid': user.id, 'username': user.username}) + return {'uuid': user.id, 'username': user.username}, 200 diff --git a/webhook_to_fedora_messaging/endpoints/util.py b/webhook_to_fedora_messaging/endpoints/util.py index 78aca64..6e4da56 100644 --- a/webhook_to_fedora_messaging/endpoints/util.py +++ b/webhook_to_fedora_messaging/endpoints/util.py @@ -1,35 +1,29 @@ -from flask import Response, Request, request, abort +from flask import Response, request, abort from functools import wraps -def not_found() -> Response: - return {'message': 'Not Found'}, 404 +def validate_request(fields=None): + fields = fields or ['username'] - -def bad_request() -> Response: - return abort(400, 'Bad Request') - - -def conflict(data: dict) -> Response: - return abort(409, data) - - -def validate_request(request: dict, fields=['username']): - return all(field in request for field in fields) - - -def unprocessable_entity() -> Response: - return {'message': 'Unprocessable Entity'}, 429 - - -def validate_request(fields: list = None): - fields = fields or ["username"] - def decorator(view): - @wraps(view) + def decorator(func): + @wraps(func) def wrapper(*args, **kwargs): - if all(field in request.json for field in fields): - return view(*args, **kwargs) - else: - abort(429, "Missing fields") + # Check if the request has JSON data + if not request.is_json: + return abort(400, {"error": "Invalid input, JSON required"}) + data = request.get_json() + # Check if all required fields are present + missing_fields = [field for field in fields if field not in data] + if missing_fields: + return abort(400, {"error": f"Missing fields: {', '.join(missing_fields)}"}) + + return func(*args, **kwargs) return wrapper + + # If the decorator is used without arguments + if callable(fields): + func = fields + fields = ['username'] + return decorator(func) + return decorator diff --git a/webhook_to_fedora_messaging/main.py b/webhook_to_fedora_messaging/main.py index 02ab300..f01eeb9 100644 --- a/webhook_to_fedora_messaging/main.py +++ b/webhook_to_fedora_messaging/main.py @@ -14,21 +14,25 @@ from .config.defaults import LOGGER_CONFIG from logging.config import dictConfig from .endpoints.user import user_endpoint +from .endpoints.service import service_endpoint from webhook_to_fedora_messaging.exceptions import ConfigError import logging def create_app(): # Instantiate a barebones Flask application - main = Flask( + app = Flask( "webhook_to_fedora_messaging" ) # First attempt loading the defaults from the Defaults object - main.config.from_object( + app.config.from_object( "webhook_to_fedora_messaging.config.defaults.Defaults" ) dictConfig(LOGGER_CONFIG) + app.register_blueprint(user_endpoint, url_prefix="/user") + app.register_blueprint(service_endpoint, url_prefix="/service") + # Then load the variables up from the custom configuration file try: confdata = get_config() @@ -36,9 +40,8 @@ def create_app(): logging.error(f"Exiting - Reason - {str(expt)}") raise - main.config.from_mapping(confdata) - db.init_app(main) + app.config.from_mapping(confdata) + db.init_app(app) dictConfig(confdata["logsconf"]) - main.register_blueprint(user_endpoint) - return main + return app diff --git a/webhook_to_fedora_messaging/test.py b/webhook_to_fedora_messaging/test.py new file mode 100644 index 0000000..935c7b3 --- /dev/null +++ b/webhook_to_fedora_messaging/test.py @@ -0,0 +1,27 @@ +from flask.blueprints import Blueprint +from flask import Flask + + +vain = Blueprint('main', __name__, url_prefix='/main') + + +# main endpoints(with prefix /main)... +@vain.route('/gey', methods=["GET"]) +def index_main(): + return "Hello world from /main/" +# routes without any prefix + + +default = Blueprint('default', __name__) + + +@default.route('/') +def index(): + return "Hello world from /" + + +def create_app(): + main = Flask(__name__) + main.register_blueprint(vain) + main.register_blueprint(default) + return main