예제 #1
0
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
예제 #2
0
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