Repository: fossasia/open-event-server

Issue:
Allow discount and access code to be accesible through it's code
**Is your feature request related to a problem? Please describe.**
<!-- A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] -->
Discount and Access codes are private to the event organizer and the admin by default. We need a mechanism to check if the discount and access code entered by the user is valid or not.

**Describe the solution you'd like**
<!-- A clear and concise description of what you want to happen. -->
We should make the discount and access code accessible through it's code.

**Additional context**
<!-- Add any other context or screenshots about the feature request here. -->
Parent: #4940 


Discussion:

Resolving patch:
diff --git a/app/api/__init__.py b/app/api/__init__.py
--- a/app/api/__init__.py
+++ b/app/api/__init__.py
@@ -386,7 +386,7 @@
 api.route(DiscountCodeListPost, 'discount_code_list_post', '/discount-codes')
 api.route(DiscountCodeList, 'discount_code_list', '/events/<int:event_id>/discount-codes',
           '/events/<event_identifier>/discount-codes', '/users/<int:user_id>/discount-codes')
-api.route(DiscountCodeDetail, 'discount_code_detail', '/discount-codes/<int:id>',
+api.route(DiscountCodeDetail, 'discount_code_detail', '/discount-codes/<int:id>', '/discount-codes/<code>',
           '/events/<int:event_id>/discount-code', '/event-invoices/<int:event_invoice_id>/discount-code')
 api.route(DiscountCodeRelationshipRequired, 'discount_code_event',
           '/discount-codes/<int:id>/relationships/event')
@@ -445,7 +445,7 @@
 api.route(AccessCodeList, 'access_code_list', '/events/<int:event_id>/access-codes',
           '/events/<event_identifier>/access-codes', '/users/<int:user_id>/access-codes',
           '/tickets/<int:ticket_id>/access-codes')
-api.route(AccessCodeDetail, 'access_code_detail', '/access-codes/<int:id>')
+api.route(AccessCodeDetail, 'access_code_detail', '/access-codes/<int:id>', '/access-codes/<code>')
 api.route(AccessCodeRelationshipRequired, 'access_code_event',
           '/access-codes/<int:id>/relationships/event')
 api.route(AccessCodeRelationshipOptional, 'access_code_user',
diff --git a/app/api/access_codes.py b/app/api/access_codes.py
--- a/app/api/access_codes.py
+++ b/app/api/access_codes.py
@@ -1,8 +1,9 @@
 from flask_rest_jsonapi import ResourceDetail, ResourceList, ResourceRelationship
+from flask_rest_jsonapi.exceptions import ObjectNotFound
 
 from app.api.bootstrap import api
 from app.api.helpers.db import safe_query
-from app.api.helpers.exceptions import ForbiddenException
+from app.api.helpers.exceptions import ForbiddenException, UnprocessableEntity
 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
@@ -75,11 +76,37 @@ def query(self, view_kwargs):
 
 class AccessCodeDetail(ResourceDetail):
     """
-    AccessCode detail by id
+    AccessCode detail by id or code
     """
+    def before_get(self, args, kwargs):
+        """
+        before get method of access code details.
+        Check for permissions on the basis of kwargs.
+        :param args:
+        :param kwargs:
+        :return:
+        """
+        # Any registered user can fetch access code details using the code.
+        if kwargs.get('code'):
+            access = db.session.query(AccessCode).filter_by(code=kwargs.get('code')).first()
+            if access:
+                kwargs['id'] = access.id
+            else:
+                raise ObjectNotFound({'parameter': '{code}'}, "Access Code:  not found")
+            return
+
+        # Co-organizer or the admin can fetch access code details using the id.
+        if kwargs.get('id'):
+            access = db.session.query(AccessCode).filter_by(id=kwargs.get('id')).one()
+            if not access:
+                raise ObjectNotFound({'parameter': '{id}'}, "Access Code:  not found")
+
+            if not has_access('is_coorganizer', event_id=access.event_id):
+                raise UnprocessableEntity({'source': ''},
+                                          "Please verify your permission")
 
-    decorators = (api.has_permission('is_coorganizer', fetch='event_id',
-                  fetch_as="event_id", model=AccessCode, methods="GET, PATCH"),
+    decorators = (jwt_required, api.has_permission('is_coorganizer', fetch='event_id',
+                  fetch_as="event_id", model=AccessCode, methods="PATCH"),
                   api.has_permission('is_coorganizer_but_not_admin', fetch='event_id',
                   fetch_as="event_id", model=AccessCode, methods="DELETE"),)
     schema = AccessCodeSchema
diff --git a/app/api/discount_codes.py b/app/api/discount_codes.py
--- a/app/api/discount_codes.py
+++ b/app/api/discount_codes.py
@@ -112,7 +112,7 @@ def query(self, view_kwargs):
 
 class DiscountCodeDetail(ResourceDetail):
     """
-    Discount Code detail by id
+    Discount Code detail by id or code.
     """
 
     def before_get(self, args, kwargs):
@@ -127,6 +127,15 @@ def before_get(self, args, kwargs):
             else:
                 kwargs['id'] = None
 
+        # Any registered user can fetch discount code details using the code.
+        if kwargs.get('code'):
+            discount = db.session.query(DiscountCode).filter_by(code=kwargs.get('code')).first()
+            if discount:
+                kwargs['id'] = discount.id
+            else:
+                raise ObjectNotFound({'parameter': '{code}'}, "DiscountCode:  not found")
+            return
+
         if kwargs.get('id'):
             discount = db.session.query(DiscountCode).filter_by(id=kwargs.get('id')).one()
             if not discount:


Test patch:
diff --git a/tests/hook_main.py b/tests/hook_main.py
--- a/tests/hook_main.py
+++ b/tests/hook_main.py
@@ -2574,6 +2574,24 @@ def discount_delete(transaction):
         db.session.commit()
 
 
+@hooks.before("Discount Codes > Get Discount Code Detail using the code > Get Discount Code Detail")
+def discount_code_get_detail_using_code(transaction):
+    """
+    GET /discount-codes/DC101
+    :param transaction:
+    :return:
+    """
+    with stash['app'].app_context():
+        event = EventFactoryBasic()
+        db.session.add(event)
+        db.session.commit()
+
+        discount_code = DiscountCodeFactory(event_id=1)
+        discount_code.code = 'DC101'
+        db.session.add(discount_code)
+        db.session.commit()
+
+
 @hooks.before("Discount Codes > List Discount Codes under a User > List All Discount Codes under a User")
 def user_discount_code_get_list(transaction):
     """
@@ -2679,6 +2697,20 @@ def access_code_delete(transaction):
         db.session.commit()
 
 
+@hooks.before("Access Codes > Access Code Detail using the Code > Access Code Detail")
+def access_code_get_detail_using_code(transaction):
+    """
+    GET /access-codes/AC101
+    :param transaction:
+    :return:
+    """
+    with stash['app'].app_context():
+        access_code = AccessCodeFactory()
+        access_code.code = 'AC101'
+        db.session.add(access_code)
+        db.session.commit()
+
+
 @hooks.before("Access Codes > Get Access Codes for an Event > List All Access Codes of an Event")
 def event_access_code_get_list(transaction):
     """