class RestAPI: # ------------------------------------------------------------------------- # Private stuff # ------------------------------------------------------------------------- # Initialize database DatabaseHandler.init_tables() DatabaseHandler.run_verification_code_clearer(timedelta(hours=12), timedelta(days=7)) # Creating our own route decorator. # It will be almost the same as app.route except it will always be on '/api/v1/' route = lambda loc, **options: app.route("/api/v1/" + loc, **options) # Simplify the exceptions handlers. # Rather than definging a function for each error or code by hands we will # store all the possible ints and Exceptions as keys and # corresponding lambda functions as values. exceptions_responses = { 400: lambda error: { "code": -1000, "msg": "An unknown error occured while processing the request.", }, 404: lambda error: { "code": -1001, "msg": "The stuff you requested for is not found.", }, IndexedException: lambda error: { "code": error.error_id, "msg": error.args[0] }, } @staticmethod def message(msg) -> str: return jsonify({"msg": msg}) @staticmethod def request_data_to_json(request) -> dict: try: return json.loads(request) except json.decoder.JSONDecodeError: raise NoJsonError() @staticmethod def check_required_fields(json, data_required: List[str]) -> None: for data in data_required: if data not in json: raise NotEnoughDataError(data_required, json.keys()) # ------------------------------------------------------------------------- # Public stuff # ------------------------------------------------------------------------- @staticmethod @route("ping", methods=["GET"]) def ping(): return RestAPI.message("pong"), 200 # ------------------------------------------------------------------------- # Registration stuff # ------------------------------------------------------------------------- @staticmethod @route("register", methods=["POST"]) def register(): if not request.json: raise NoJsonError() data_required = [ "email", "password", ] for data in data_required: if data not in request.json: raise NotEnoughDataError(data_required, request.json.keys()) user.begin_email_verification( request.json["email"], request.json["password"], ) return RestAPI.message( f"Verification is sent to {request.json['email']}"), 201 @staticmethod @route("register/verify/<string:verification_hash>") def confirm_verification(verification_hash): return user.verify_email_from_code(verification_hash), 201 # ------------------------------------------------------------------------- # User stuff # ------------------------------------------------------------------------- @staticmethod @route("user", methods=["GET"]) @user.login_required def check_user(): return jsonify(user.get_data()), 200 @staticmethod @route("user", methods=["PUT"]) @user.login_required def edit_user_data(): try: request_json = json.loads(request.data) except json.decoder.JSONDecodeError: raise NoJsonError() data_required = { "phone": "phone_number", "name": "name", } for data in data_required: if data in request_json: user.edit_data(data_required[data], request_json[data]) return RestAPI.message("Data is edited successful"), 201 @staticmethod @route("user/avatar", methods=["GET", "POST", "DELETE"]) @user.login_required def edit_avatar(): if request.method == "GET": user.get_avatar_link() return jsonify({"link": user.get_avatar_link()}), 200 if request.method == "POST": user.add_avatar(request.files["file"]) return RestAPI.message("New avatar is saved"), 201 if request.method == "DELETE": user.delete_avatar() return RestAPI.message("Your avatar is now deleted"), 201 # ------------------------------------------------------------------------- # Lots stuff # ------------------------------------------------------------------------- @staticmethod @route("lots", methods=["POST"]) @user.login_required def create_lot(): request_json = RestAPI.request_data_to_json(request.data) data_required = [ "name", "amount", "currency", "term", "return_way", "security", "percentage", "form", "commentary", ] RestAPI.check_required_fields(request_json, data_required) return ( jsonify({ "lot_id": user.create_lot( *[request_json[data] for data in data_required]) }), 201, ) @staticmethod @route("lots/approved", methods=["GET"]) @route("lots", methods=["GET"]) def get_approved_lots(): return jsonify(Lot.get_all_approved_lots()), 200 @staticmethod @route("lots/<int:lot_id>", methods=["GET"]) def get_lot(lot_id): return jsonify(Lot.get_lot(lot_id)), 200 @staticmethod @route("lots/<int:lot_id>", methods=["PUT"]) @user.login_required def update_lot(lot_id): if not Lot.can_user_edit(user.email(), lot_id): raise NoPermissionError() if not request.json: raise NoJsonError() data_available = [ "name", "amount", "currency", "term", "return_way", "security", "percentage", "form", "commentary", ] for data in data_available: if data in request.json: Lot.update_data(lot_id, data, request.json[data]) return RestAPI.message("A lot is changed"), 201 @staticmethod @route("lots/<int:lot_id>", methods=["DELETE"]) @user.login_required def delete_lot(lot_id): if not Lot.can_user_edit(user.email(), lot_id): raise NoPermissionError() Lot.delete_lot(lot_id) return RestAPI.message("A lot is deleted"), 201 @staticmethod @route("lots/<int:lot_id>", methods=["POST"]) @user.login_required def restore_lot(lot_id): if not Lot.can_user_edit(user.email(), lot_id): raise NoPermissionError() Lot.restore_lot(lot_id) return RestAPI.message("A lot is restored"), 201 @staticmethod @route("lots/<int:lot_id>/photos", methods=["GET"]) def get_lot_photo(lot_id): return jsonify({"link": Lot.get_photos(lot_id)}), 200 @staticmethod @route("lots/<int:lot_id>/photos", methods=["POST"]) @user.login_required def add_lot_photo(lot_id): if not Lot.can_user_edit(user.email(), lot_id): raise NoPermissionError() a = request.files resp = { filename: Lot.add_photo(request.files[filename], lot_id) for filename in request.files } return jsonify(resp), 201 @staticmethod @route("lots/<int:lot_id>/photos/<int:photo_id>", methods=["DELETE"]) @user.login_required def remove_lot_photo(lot_id, photo_id): if not Lot.can_user_edit(user.email(), lot_id): raise NoPermissionError() return jsonify(Lot.remove_photo(lot_id, photo_id)), 201 @staticmethod @route("lots/favorites/<int:lot_id>", methods=["PUT", "DELETE"]) @user.login_required def update_favorite_lots(lot_id): if request.method == "PUT": user.add_lot_to_favorites(lot_id) return RestAPI.message("A lot is added to favorites"), 201 if request.method == "DELETE": user.remove_lot_from_favorites(lot_id) return RestAPI.message("A lot is removed from favorites"), 201 @staticmethod @route("lots/favorites", methods=["GET"]) @user.login_required def get_favorite_lots(): return jsonify(user.get_favorites()), 200 @staticmethod @route("lots/personal", methods=["GET"]) @user.login_required def get_personal_lots(): return jsonify(user.get_personal()), 200 @staticmethod @route("lots/personal/deleted", methods=["GET"]) @user.login_required def get_personal_deleted_lots(): return jsonify(user.get_personal_deleted()), 200 @staticmethod @route("lots/subscription/<int:lot_id>", methods=["PUT"]) @user.login_required def subscribe_to_lot(lot_id): request_json = RestAPI.request_data_to_json(request.data) data_required = [ "type", "message", ] RestAPI.check_required_fields(request_json, data_required) if user.subscribe_to_lot( lot_id, *[request_json[data] for data in data_required]): return RestAPI.message( f"You are now subscribed to lot {lot_id}"), 201 else: return RestAPI.message("You are already subscribed"), 200 @staticmethod @route("lots/subscription/<int:lot_id>", methods=["DELETE"]) @user.login_required def unsubscribe_from_lot(lot_id): try: user.unsubscribe_from_lot(lot_id) return RestAPI.message( f"You are no longer subscribed to lot {lot_id}"), 201 except: return RestAPI.message("You are not subscribed"), 200 @staticmethod @route("lots/subscription", methods=["GET"]) @user.login_required def get_subscribed_lots(): return jsonify({"lots": user.get_subscriptions()}), 200 # ------------------------------------------------------------------------- # Moderator stuff # ------------------------------------------------------------------------- @staticmethod @route("lots/<int:lot_id>/approve", methods=["PUT"]) @moderator.login_required def approve_lot(lot_id): Lot.approve(lot_id) return RestAPI.message("A lot is now approved"), 201 @staticmethod @route("lots/<int:lot_id>/security", methods=["PUT", "DELETE"]) @moderator.login_required def set_security_checked(lot_id): if request.type == "PUT": Lot.set_security_checked(lot_id, True) return RestAPI.message("Lot's security is now checked"), 201 if request.type == "DELETE": Lot.set_security_checked(lot_id, False) return RestAPI.message("Lot's security is no more checked"), 201 @staticmethod @route("lots/unapproved", methods=["GET"]) @moderator.login_required def get_unapproved_lots(): return jsonify(Lot.get_all_unapproved_lots()), 200 @staticmethod @route("lots/subscription/approved", methods=["GET"]) @moderator.login_required def get_approved_subscriptions(): return jsonify({"lots": Lot.get_approved_subscriptions()}), 200 @staticmethod @route("lots/subscription/unapproved", methods=["GET"]) @moderator.login_required def get_unapproved_subscriptions(): return jsonify({"lots": Lot.get_unapproved_subscriptions()}), 200
class RestAPI: # ------------------------------------------------------------------------- # Private stuff # ------------------------------------------------------------------------- # Initialize database DatabaseHandler.init_tables() DatabaseHandler.run_verification_code_clearer( timedelta(hours=12), timedelta(days=7) ) # Creating our own route decorator. # It will be almost the same as app.route except it will always be on '/api/v1/' route = lambda loc, **options: app.route('/api/v1/' + loc, **options) # Simplify the exceptions handlers. # Rather than definging a function for each error or code by hands we will # store all the possible ints and Exceptions as keys and # corresponding lambda functions as values. exceptions_responses = { 400: lambda error: {'code': -1000, 'msg': 'An unknown error occured while processing the request.'}, 404: lambda error: {'code': -1001, 'msg': 'The stuff you requested for is not found.'}, IndexedException: lambda error: {'code': error.error_id, 'msg': error.args[0]}, NotEnoughDataError: lambda error: {'code': error.error_id, 'msg': error.args[0]}, } @staticmethod def message(msg) -> str: return jsonify( { 'msg': msg } ) # ------------------------------------------------------------------------- # Public stuff # ------------------------------------------------------------------------- @staticmethod @route('ping', methods=['GET']) def ping(): return RestAPI.message('pong'), 200 # ------------------------------------------------------------------------- # Registration stuff # ------------------------------------------------------------------------- @staticmethod @route('register', methods=['POST']) def register(): if not request.json: raise NoJsonError() data_required = [ 'email', 'password', ] for data in data_required: if data not in request.json: raise NotEnoughDataError(data_required, request.json.keys()) user.begin_email_verification( request.json['email'], request.json['password'], ) return RestAPI.message(f"Verification is sent to {request.json['email']}"), 201 @staticmethod @route('register/verify/<string:verification_hash>') def confirm_verification(verification_hash): return user.verify_email_from_code(verification_hash), 201 # ------------------------------------------------------------------------- # User stuff # ------------------------------------------------------------------------- @staticmethod @route('user', methods=['GET']) @user.login_required def check_user(): return jsonify(user.get_data()), 200 @staticmethod @route('user', methods=['PUT']) @user.login_required def edit_user_data(): try: request_json = json.loads(request.data) except: raise NoJsonError() data_required = { 'phone': 'phone_number', 'name': 'name', } for data in data_required: if data in request_json: user.edit_data(data_required[data], request_json[data]) return RestAPI.message('Data is edited successful'), 201 @staticmethod @route('user/avatar', methods=['GET', 'POST', 'DELETE']) @user.login_required def edit_avatar(): if request.method == 'GET': user.get_avatar_link() return jsonify({'link': user.get_avatar_link()}), 200 if request.method == 'POST': user.add_avatar(request.files['file']) return RestAPI.message('New avatar is saved'), 201 if request.method == 'DELETE': user.delete_avatar() return RestAPI.message('Your avatar is now deleted'), 201 # ------------------------------------------------------------------------- # Lots stuff # ------------------------------------------------------------------------- @staticmethod @route('lots', methods=['POST']) @user.login_required def create_lot(): if not request.json: raise NoJsonError() data_required = [ 'name', 'amount', 'currency', 'term', 'return_way', 'security', 'percentage', 'form', 'commentary' ] for data in data_required: if data not in request.json: raise NotEnoughDataError(data_required, request.json.keys()) return jsonify({'lot_id': user.create_lot(*[request.json[data] for data in data_required]) }), 201 @staticmethod @route('lots/approved', methods=['GET']) @route('lots', methods=['GET']) def get_approved_lots(): return jsonify(Lot.get_all_approved_lots()), 200 @staticmethod @route('lots/<int:lot_id>', methods=['GET']) def get_lot(lot_id): return jsonify(Lot.get_lot(lot_id)), 200 @staticmethod @route('lots/<int:lot_id>', methods=['PUT']) @user.login_required def update_lot(lot_id): if not Lot.can_user_edit(user.email(), lot_id): raise NoPermissionError() if not request.json: raise NoJsonError() data_available = [ 'name', 'amount', 'currency', 'term', 'return_way', 'security', 'percentage', 'form', 'commentary' ] for data in data_available: if data in request.json: Lot.update_data(lot_id, data, request.json[data]) return RestAPI.message('A lot is changed'), 201 @staticmethod @route('lots/<int:lot_id>', methods=['DELETE']) @user.login_required def delete_lot(lot_id): if not Lot.can_user_edit(user.email(), lot_id): raise NoPermissionError() Lot.delete_lot(lot_id) return RestAPI.message('A lot is deleted'), 201 @staticmethod @route('lots/<int:lot_id>', methods=['POST']) @user.login_required def restore_lot(lot_id): if not Lot.can_user_edit(user.email(), lot_id): raise NoPermissionError() Lot.restore_lot(lot_id) return RestAPI.message('A lot is restored'), 201 @staticmethod @route('lots/<int:lot_id>/photos', methods=['GET']) def get_lot_photo(lot_id): return jsonify({'link': Lot.get_photos(lot_id)}), 200 @staticmethod @route('lots/<int:lot_id>/photos', methods=['POST']) @user.login_required def add_lot_photo(lot_id): if not Lot.can_user_edit(user.email(), lot_id): raise NoPermissionError() return jsonify(Lot.add_photo(request.files['file'], lot_id)), 201 @staticmethod @route('lots/<int:lot_id>/photos/<int:photo_id>', methods=['DELETE']) @user.login_required def remove_lot_photo(lot_id, photo_id): if not Lot.can_user_edit(user.email(), lot_id): raise NoPermissionError() return jsonify(Lot.remove_photo(lot_id, photo_id)), 201 @staticmethod @route('lots/favorites/<int:lot_id>', methods=['PUT', 'DELETE']) @user.login_required def update_favorite_lots(lot_id): if request.method == 'PUT': user.add_lot_to_favorites(lot_id) return RestAPI.message('A lot is added to favorites'), 201 if request.method == 'DELETE': user.remove_lot_from_favorites(lot_id) return RestAPI.message('A lot is removed from favorites'), 201 @staticmethod @route('lots/favorites', methods=['GET']) @user.login_required def get_favorite_lots(): return jsonify(user.get_favorites()), 200 @staticmethod @route('lots/personal', methods=['GET']) @user.login_required def get_personal_lots(): return jsonify(user.get_personal()), 200 @staticmethod @route('lots/personal/deleted', methods=['GET']) @user.login_required def get_personal_deleted_lots(): return jsonify(user.get_personal_deleted()), 200 @staticmethod @route('lots/subscription/<int:lot_id>', methods=['PUT']) @user.login_required def subscribe_to_lot(lot_id): try: user.subscribe_to_lot(lot_id) return RestAPI.message(f'You are now subscribed to lot {lot_id}'), 201 except: return RestAPI.message('You are already subscribed'), 200 @staticmethod @route('lots/subscription/<int:lot_id>', methods=['DELETE']) @user.login_required def unsubscribe_from_lot(lot_id): try: user.unsubscribe_from_lot(lot_id) return RestAPI.message(f'You are no longer subscribed to lot {lot_id}'), 201 except: return RestAPI.message('You are not subscribed'), 200 @staticmethod @route('lots/subscription', methods=['GET']) @user.login_required def get_subscribed_lots(): return jsonify({'lots': user.get_subscriptions()}), 200 # ------------------------------------------------------------------------- # Moderator stuff # ------------------------------------------------------------------------- @staticmethod @route('lots/<int:lot_id>/approve', methods=['PUT']) @moderator.login_required def approve_lot(lot_id): Lot.approve(lot_id) return RestAPI.message('A lot is now approved'), 201 @staticmethod @route('lots/<int:lot_id>/security', methods=['PUT', 'DELETE']) @moderator.login_required def set_security_checked(lot_id): if request.type == 'PUT': Lot.set_security_checked(lot_id, True) return RestAPI.message('Lot\'s security is now checked'), 201 if request.type == 'DELETE': Lot.set_security_checked(lot_id, False) return RestAPI.message('Lot\'s security is no more checked'), 201 @staticmethod @route('lots/unapproved', methods=['GET']) @moderator.login_required def get_unapproved_lots(): return jsonify(Lot.get_all_unapproved_lots()), 200 @staticmethod @route('lots/subscription/approved', methods=['GET']) @moderator.login_required def get_approved_subscriptions(): return jsonify({'lots': Lot.get_approved_subscriptions()}), 200 @staticmethod @route('lots/subscription/unapproved', methods=['GET']) @moderator.login_required def get_unapproved_subscriptions(): return jsonify({'lots': Lot.get_unapproved_subscriptions()}), 200