Repository: fossasia/open-event-server

Issue:
Refactor code for custom endpoints.
**Describe the bug**
Custom endpoints should be shifted to a different folder so as to solve the problem of circular dependency in case of Limiter functionality.  And also for a better design and follow single responsibility principle.

**Additional context**
On it.


Discussion:
@iamareebjamal @prateekj117  - the custom endpoints refers to the endpoints like this

https://github.com/fossasia/open-event-server/blob/b69df88f6d802aefd046865ed0ef0e41f5c00797/app/api/attendees.py#L31

So, we have to move this to the custom folder with filename `attendee.py` and just refactor and make this work.

Am I correct?

Resolving patch:
diff --git a/app/api/attendees.py b/app/api/attendees.py
--- a/app/api/attendees.py
+++ b/app/api/attendees.py
@@ -1,10 +1,7 @@
 import datetime
 
-from flask import Blueprint, request, jsonify, abort, make_response
 from flask_jwt_extended import current_user
 from flask_rest_jsonapi import ResourceDetail, ResourceList, ResourceRelationship
-from flask_rest_jsonapi.exceptions import ObjectNotFound
-from sqlalchemy.orm.exc import NoResultFound
 from sqlalchemy import or_, and_
 
 from app.api.bootstrap import api
@@ -14,7 +11,6 @@
     ForbiddenException,
     UnprocessableEntity,
 )
-from app.api.helpers.mail import send_email_to_attendees
 from app.api.helpers.permission_manager import has_access
 from app.api.helpers.permissions import jwt_required
 from app.api.helpers.query import event_query
@@ -28,8 +24,6 @@
 
 from app.settings import get_settings
 
-attendee_misc_routes = Blueprint('attendee_misc', __name__, url_prefix='/v1')
-
 
 def get_sold_and_reserved_tickets_count(event_id):
     order_expiry_time = get_settings()['order_expiry_time']
@@ -275,34 +269,3 @@ class AttendeeRelationshipOptional(ResourceRelationship):
     schema = AttendeeSchema
     data_layer = {'session': db.session,
                   'model': TicketHolder}
-
-
-@attendee_misc_routes.route('/attendees/send-receipt', methods=['POST'])
-@jwt_required
-def send_receipt():
-    """
-    Send receipts to attendees related to the provided order.
-    :return:
-    """
-    order_identifier = request.json.get('order-identifier')
-    if order_identifier:
-        try:
-            order = db.session.query(Order).filter_by(identifier=order_identifier).one()
-        except NoResultFound:
-            raise ObjectNotFound({'parameter': '{identifier}'}, "Order not found")
-
-        if (order.user_id != current_user.id) and (not has_access('is_registrar', event_id=order.event_id)):
-            abort(
-                make_response(jsonify(error="You need to be the event organizer or order buyer to send receipts."), 403)
-            )
-        elif order.status != 'completed':
-            abort(
-                make_response(jsonify(error="Cannot send receipt for an incomplete order"), 409)
-            )
-        else:
-            send_email_to_attendees(order, current_user.id)
-            return jsonify(message="receipt sent to attendees")
-    else:
-        abort(
-            make_response(jsonify(error="Order identifier missing"), 422)
-        )
diff --git a/app/api/custom/attendees.py b/app/api/custom/attendees.py
new file mode 100644
--- /dev/null
+++ b/app/api/custom/attendees.py
@@ -0,0 +1,45 @@
+from flask import Blueprint, request, jsonify, abort, make_response
+from flask_jwt_extended import current_user
+from sqlalchemy.orm.exc import NoResultFound
+
+from app.api.helpers.mail import send_email_to_attendees
+from app.api.helpers.permissions import jwt_required
+from app.api.helpers.errors import (
+    UnprocessableEntityError,
+    NotFoundError,
+    ForbiddenError,
+)
+from app.api.helpers.permission_manager import has_access
+
+from app.models.order import Order
+from app.models import db
+
+attendee_blueprint = Blueprint('attendee_blueprint', __name__, url_prefix='/v1')
+
+
+@attendee_blueprint.route('/attendees/send-receipt', methods=['POST'])
+@jwt_required
+def send_receipt():
+    """
+    Send receipts to attendees related to the provided order.
+    :return:
+    """
+    order_identifier = request.json.get('order-identifier')
+    if order_identifier:
+        try:
+            order = db.session.query(Order).filter_by(identifier=order_identifier).one()
+        except NoResultFound:
+            return NotFoundError({'parameter': '{order_identifier}'}, "Order not found").respond()
+
+        if (order.user_id != current_user.id) and (not has_access('is_registrar', event_id=order.event_id)):
+            return ForbiddenError({'source': ''},
+                                  'You need to be the event organizer or order buyer to send receipts.').respond()
+        elif order.status != 'completed':
+            abort(
+                make_response(jsonify(error="Cannot send receipt for an incomplete order"), 409)
+            )
+        else:
+            send_email_to_attendees(order, current_user.id)
+            return jsonify(message="receipt sent to attendees")
+    else:
+        return UnprocessableEntityError({'source': ''}, 'Order identifier missing').respond()
diff --git a/app/instance.py b/app/instance.py
--- a/app/instance.py
+++ b/app/instance.py
@@ -142,7 +142,7 @@ def create_app():
     with app.app_context():
         from app.api.admin_statistics_api.events import event_statistics
         from app.api.auth import auth_routes
-        from app.api.attendees import attendee_misc_routes
+        from app.api.custom.attendees import attendee_blueprint
         from app.api.bootstrap import api_v1
         from app.api.celery_tasks import celery_routes
         from app.api.event_copy import event_copy
@@ -170,7 +170,7 @@ def create_app():
         app.register_blueprint(auth_routes)
         app.register_blueprint(event_statistics)
         app.register_blueprint(user_misc_routes)
-        app.register_blueprint(attendee_misc_routes)
+        app.register_blueprint(attendee_blueprint)
         app.register_blueprint(order_misc_routes)
         app.register_blueprint(role_invites_misc_routes)
         app.register_blueprint(authorised_blueprint)


Test patch: