class Auth:
    def login_required(*args, **kwargs):
    def current_user(self):
    def init_app(app):
def load_user(user_id):
def load_user_from_request(request): ENVIRONMENT = os.getenv('ENVIRONMENT', 'dev')
class BaseModel(Model):
    class Meta:
class Item(BaseModel):
class Picture(BaseModel):
class User(BaseModel, UserMixin):
class Address(BaseModel):
class Order(BaseModel):
    class Meta:
class OrderItem(BaseModel):
class Favorite(BaseModel):
    def save(self, *args, **kwargs):
    def json_list(cls, objs_list):
    def json(self, include_data=[]):
    def validate_input(cls, data, partial=False):
    def search(cls, query, dataset, limit=-1,
               attributes=None, weights=None,
               threshold=search.config.THRESHOLD):
    def __str__(self):
    def is_favorite(self, item):
def on_delete_item_handler(model_class, instance):
    def filename(self):
    def __str__(self):
def on_delete_picture_handler(model_class, instance):
    def exists(email):
    def hash_password(password):
    def verify_password(self, password):
    def add_favorite(user, item):
    def delete_favorite(self, obj):
    def order_items(self):
    def empty_order(self):
    def create_order(user, address, items):
    def update_items(self, items, update_total=True, new_address=None):
    def edit_items_quantity(self, items):
    def delete_items(self, items):
    def create_items(self, items):
    def add_item(self, item, quantity=1):
    def remove_item(self, item, quantity=1):
    def add_item(self, quantity=1):
    def remove_item(self, quantity=1):
    def _calculate_subtotal(self): class AddressesHandler(Resource):
    """ Addresses endpoint. """
    @auth.login_required
    def get(self):
        user_addrs = list(Address.select().where(
            Address.user == auth.current_user))

        return generate_response(Address.json_list(user_addrs), OK)

    @auth.login_required
    def post(self):
        res = request.get_json(force=True)
        errors = Address.validate_input(res)
        if errors:
            return errors, BAD_REQUEST

        data = res['data']['attributes']

        addr = Address.create(
            uuid=uuid.uuid4(),
            user=auth.current_user,
            country=data['country'],
            city=data['city'],
            post_code=data['post_code'],
            address=data['address'],
            phone=data['phone'])

        return generate_response(addr.json(), CREATED) class AddressHandler(Resource):
    """ Address endpoint. """
    @auth.login_required
    def get(self, address_uuid):
        try:
            addr = Address.get(
                Address.user == auth.current_user,
                Address.uuid == address_uuid
            )
            return generate_response(addr.json(), OK)

        except Address.DoesNotExist:
            return None, NOT_FOUND

    @auth.login_required
    def patch(self, address_uuid):
        try:
            obj = Address.get(Address.user == auth.current_user,
                              Address.uuid == address_uuid)
        except Address.DoesNotExist:
            return None, NOT_FOUND

        res = request.get_json(force=True)

        errors = Address.validate_input(res)
        if errors:
            return errors, BAD_REQUEST

        data = res['data']['attributes']

        country = data.get('country')
        city = data.get('city')
        post_code = data.get('post_code')
        address = data.get('address')
        phone = data.get('phone')

        if country and country != obj.country:
            obj.country = country

        if city and city != obj.city:
            obj.city = city

        if post_code and post_code != obj.post_code:
            obj.post_code = post_code

        if address and address != obj.address:
            obj.address = address

        if phone and phone != obj.phone:
            obj.phone = phone

        obj.save()

        return generate_response(obj.json(), OK)

    @auth.login_required
    def delete(self, address_uuid):
        result = Address.delete().where(
            Address.user == auth.current_user,
            Address.uuid == address_uuid,
        ).execute()

        if result == 0:
            return None, NOT_FOUND

        return None, NO_CONTENT class LoginHandler(Resource):
    """Handler of the login authentication"""

    @cross_origin(supports_credentials=True)
    def post(self):
        request_data = request.get_json(force=True)

        if 'email' not in request_data or 'password' not in request_data:
            abort(client.BAD_REQUEST)

        email = request_data['email']
        password = request_data['password']

        try:
            user = User.get(User.email == email)
        except User.DoesNotExist:
            abort(client.BAD_REQUEST)

        if not user.verify_password(password):
            abort(client.UNAUTHORIZED)

        login_user(user)
        return generate_response({}, client.OK) class LogoutHandler(Resource):
    """Handler to logout users. """

    def post(self):
        logout_user()
        return generate_response({}, client.OK) class OrdersHandler(Resource):
    """ Orders endpoint. """

    def get(self):
        """ Get all the orders."""
        data = Order.json_list(Order.select())
        return generate_response(data, OK)

    @auth.login_required
    def post(self):
        """ Insert a new order."""
        res = request.get_json(force=True)

        errors = Order.validate_input(res)
        if errors:
            return errors, BAD_REQUEST

        # Extract data to create the new order
        req_items = res['data']['relationships']['items']['data']
        req_address = res['data']['relationships']['delivery_address']['data']
        req_user = res['data']['relationships']['user']['data']

        # Check that the address exist
        try:
            user = User.get(User.uuid == req_user['id'])
        except User.DoesNotExist:
            abort(BAD_REQUEST)

        # get the user from the flask.g global object registered inside the
        # auth.py::verify() function, called by @auth.login_required decorator
        # and match it against the found user.
        # This is to prevent users from creating other users' order.
        if auth.current_user != user and auth.current_user.admin is False:
            return ({'message': "You can't create a new order for another user"},
                    UNAUTHORIZED)

        # Check that the items exist
        item_ids = [req_item['id'] for req_item in req_items]
        items = Item.select().where(Item.uuid << item_ids)
        if items.count() != len(req_items):
            abort(BAD_REQUEST)

        # Check that the address exist
        try:
            address = Address.get(Address.uuid == req_address['id'])
        except Address.DoesNotExist:
            abort(BAD_REQUEST)

        # Generate the dict of {<Item>: <int:quantity>} to call Order.create_order
        items_to_add = {}
        for req_item in req_items:
            item = next(i for i in items if str(i.uuid) == req_item['id'])
            items_to_add[item] = req_item['quantity']
        with database.atomic():
            try:
                order = Order.create(
                    delivery_address=address,
                    user=auth.current_user,
                )

                for item in items:
                    for req_item in req_items:
                        # if names match add item and quantity, once per
                        # req_item
                        if str(item.uuid) == req_item['id']:
                            order.add_item(item, req_item['quantity'])
                            break
                notify_new_order(address=order.delivery_address, user=order.user)
            except InsufficientAvailabilityException:
                abort(BAD_REQUEST)

        return generate_response(order.json(), CREATED) class OrderHandler(Resource):
    """ Single order endpoints."""

    def get(self, order_uuid):
        """ Get a specific order, including all the related Item(s)."""
        try:
            order = Order.get(Order.uuid == order_uuid)
        except Order.DoesNotExist:
            return None, NOT_FOUND

        return generate_response(order.json(), OK)

    @auth.login_required
    def patch(self, order_uuid):
        """ Modify a specific order. """
        res = request.get_json(force=True)

        errors = Order.validate_input(res, partial=True)
        if errors:
            return errors, BAD_REQUEST

        data = res['data']['relationships']
        req_items = data.get('items', {})
        req_address = data.get('delivery_address')

        with database.atomic():
            try:
                order = Order.get(uuid=str(order_uuid))
            except Order.DoesNotExist:
                abort(NOT_FOUND)

            address = None
            if req_address:
                try:
                    address = Address.get(Address.uuid == req_address['data']['id'])
                except Address.DoesNotExist:
                    abort(BAD_REQUEST)
                order.delivery_address = address

            # get the user from the flask.g global object registered inside the
            # auth.py::verify() function, called by @auth.login_required decorator
            # and match it against the found user.
            # This is to prevent uses from modify other users' order.
            if auth.current_user != order.user and auth.current_user.admin is False:
                return ({'message': "You can't delete another user's order"},
                        UNAUTHORIZED)

            # Generate the dict of {<Item>: <int:quantity>} to call Order.update_items
            items_uuids = [e['id'] for e in req_items.get('data', [])]
            items = list(Item.select().where(Item.uuid << items_uuids))
            if len(items) != len(items_uuids):
                abort(BAD_REQUEST)
            items_to_add = {
                item: req_item['quantity']
                for item in items for req_item in req_items.get('data', [])
                if str(item.uuid) == req_item['id']
            }
            try:
                order.update_items(items_to_add, new_address=address)
            except InsufficientAvailabilityException:
                abort(BAD_REQUEST)

        return generate_response(order.json(), OK)

    @auth.login_required
    def delete(self, order_uuid):
        """ Delete a specific order. """
        try:
            obj = Order.get(uuid=str(order_uuid))
        except Order.DoesNotExist:
            return None, NOT_FOUND

        # get the user from the flask.g global object registered inside the
        # auth.py::verify() function, called by @auth.login_required decorator
        # and match it against the found user.
        # This is to prevent users from deleting other users' account.
        if auth.current_user != obj.user and auth.current_user.admin is False:
            return ({'message': "You can't delete another user's order"},
                    UNAUTHORIZED)

        obj.delete_instance(recursive=True)
        return None, NO_CONTENT class ItemHandler(Resource):
    """Handler of a specific item"""

    def get(self, item_uuid):
        """Retrieve the item specified by item_uuid"""
        try:
            item = Item.get(Item.uuid == item_uuid)
            return generate_response(item.json(), client.OK)
        except Item.DoesNotExist:
            return None, client.NOT_FOUND

    def patch(self, item_uuid):
        """Edit the item specified by item_uuid"""
        try:
            obj = Item.get(Item.uuid == item_uuid)
        except Item.DoesNotExist:
            return None, client.NOT_FOUND

        request_data = request.get_json(force=True)

        errors = Item.validate_input(request_data, partial=True)
        if errors:
            return errors, client.BAD_REQUEST

        data = request_data['data']['attributes']

        name = data.get('name')
        price = data.get('price')
        description = data.get('description')
        availability = data.get('availability')
        category = data.get('category')

        if name:
            obj.name = name

        if price:
            obj.price = price

        if description:
            obj.description = description

        if availability:
            obj.availability = availability

        if category:
            obj.category = category

        obj.save()

        return generate_response(obj.json(), client.OK)

    def delete(self, item_uuid):
        """Remove the item specified by item_uuid"""
        try:
            item = Item.get(Item.uuid == item_uuid)
        except Item.DoesNotExist:
            return None, client.NOT_FOUND

        item.delete_instance()
        return None, client.NO_CONTENT class ItemsHandler(Resource):
    """Handler of the collection of items"""

    def get(self):
        """Retrieve every item"""
        data = Item.json_list(Item.select())
        return generate_response(data, client.OK)

    def post(self):
        """
        Insert a new item, the item_uuid identifier is forwarded
        from the one generated from the database
        """
        request_data = request.get_json(force=True)

        errors = Item.validate_input(request_data)
        if errors:
            return errors, client.BAD_REQUEST

        data = request_data['data']['attributes']

        if int(data['availability']) < 0:
            return None, client.BAD_REQUEST

        item = Item.create(
            uuid=uuid.uuid4(),
            name=data['name'],
            price=float(data['price']),
            description=data['description'],
            availability=int(data['availability']),
            category=data['category'],
        )

        return generate_response(item.json(), client.CREATED) class SearchItemHandler(Resource):
    def get(self):
        query = request.args.get('query')
        limit = int(request.args.get('limit', -1))
        min_limit, max_limit = 0, 100

        limit_in_range = limit > min_limit and limit <= max_limit

        if query is not None and limit_in_range:
            matches = Item.search(query, Item.select(), limit)
            return generate_response(Item.json_list(matches), client.OK)

        def fmt_error(msg):
            return {'detail': msg}

        errors = {"errors": []}

        if not query:
            errors['errors'].append(fmt_error('Missing query.'))

        if not limit_in_range:
            msg = 'Limit out of range. must be between {} and {}. Requested: {}'
            errors['errors'].append(
                fmt_error(msg.format(min_limit, max_limit, limit)))

        return errors, client.BAD_REQUEST class UsersHandler(Resource):
    """
    Handler for main user endpoint.

    Implements:
    * `get` method to retrieve the list of all the users
    * `post` method to add a new user to the database.
    """
    @auth.login_required
    def get(self):
        if not auth.current_user.admin:
            return ({'message': "You can't get the list users."}, UNAUTHORIZED)

        data = User.json_list(User.select())
        return generate_response(data, OK)

    def post(self):
        """ Add an user to the database."""
        data = request.get_json(force=True)

        errors = User.validate_input(data)
        if errors:
            return errors, BAD_REQUEST

        # Extract the user attributes to check and generate the User row
        data = data['data']['attributes']

        # If email is present in the database return a BAD_REQUEST response.
        if User.exists(data['email']):
            msg = {'message': 'email already present.'}
            return msg, CONFLICT

        new_user = User.create(
            uuid=uuid.uuid4(),
            first_name=data['first_name'],
            last_name=data['last_name'],
            email=data['email'],
            password=User.hash_password(data['password'])
        )
        notify_new_user(first_name=new_user.first_name,
                        last_name=new_user.last_name)

        # If everything went OK return the newly created user and CREATED code
        # TODO: Handle json() return value (data, errors) and handle errors not
        # empty
        return generate_response(new_user.json(), CREATED)

    @auth.login_required
    def patch(self):
        """Edit the current logged user"""

        request_data = request.get_json(force=True)

        errors = User.validate_input(request_data, partial=True)
        if errors:
            return errors, BAD_REQUEST

        user = auth.current_user
        data = request_data['data']['attributes']

        first_name = data.get('first_name')
        last_name = data.get('last_name')
        email = data.get('email')

        if first_name:
            user.first_name = first_name

        if last_name:
            user.last_name = last_name

        if email:
            user.email = email

        user.save()

        return generate_response(user.json(), OK)

    @auth.login_required
    def delete(self):
        """
        Delete the current logged user from the database.
        """
        user = auth.current_user

        user.delete_instance(recursive=True)
        return None, NO_CONTENT class UserHandler(Resource):
    """
    Handler for main user endpoint.

    Implements:
    * `get` method to retrieve the current user
    """
    @auth.login_required
    def get(self):
        return generate_response(auth.current_user.json(), OK) class PictureHandler(Resource):
    """Handler of a specific picture"""

    def get(self, picture_uuid):
        """Retrieve the picture specified by picture_uuid"""
        try:
            picture = Picture.get(Picture.uuid == picture_uuid)
        except Picture.DoesNotExist:
            return None, client.NOT_FOUND

        return send_from_directory(utils.get_image_folder(),
                                   picture.filename, as_attachment=True)

    def delete(self, picture_uuid):
        """Remove the picture specified by picture_uuid"""
        try:
            obj = Picture.get(Picture.uuid == picture_uuid)
        except Picture.DoesNotExist:
            return None, client.NOT_FOUND
        obj.delete_instance()
        return None, client.NO_CONTENT class ItemPictureHandler(Resource):

    def get(self, item_uuid):
        """Retrieve every picture of an item"""
        pictures = Picture.select().join(Item).where(Item.uuid == item_uuid)

        if pictures:
            data = Picture.json_list(pictures)
            return generate_response(data, client.OK)

        return None, client.NOT_FOUND

    def post(self, item_uuid):
        """Insert a new picture for the specified item"""
        if 'image' not in request.files:
            return {"message": "No image received"},\
                client.BAD_REQUEST

        try:
            item = Item.get(Item.uuid == item_uuid)
        except Item.DoesNotExist:
            return None, client.NOT_FOUND

        file = request.files['image']
        picture_uuid = uuid.uuid4()
        extension = os.path.splitext(file.filename)[1][1:]

        if extension not in ALLOWED_EXTENSION:
            return {"message": "File extension not allowed"},\
                client.BAD_REQUEST

        utils.save_image(file, picture_uuid, extension)

        return Picture.create(
            uuid=picture_uuid,
            extension=extension,
            item=item
        ).json(), client.CREATED class FavoritesHandler(Resource):
    @auth.login_required
    def get(self):
        data = Favorite.json_list(auth.current_user.favorites)

        return generate_response(data, OK)

    @auth.login_required
    def post(self):
        user = auth.current_user

        res = request.get_json(force=True)
        errors = Favorite.validate_input(res)
        if errors:
            return errors, BAD_REQUEST

        data = res['data']['attributes']

        try:
            item = Item.get(Item.uuid == data['item_uuid'])
        except Item.DoesNotExist:
            return {"message": "Item {} doesn't exist as Favorite.".format(
                    data['item_uuid'])}, NOT_FOUND

        has_already = Item.is_favorite(user, item)
        if has_already:
            return {"message": "Item {} was already been inserted as Favorite.".format(
                    data['item_uuid'])}, OK

        favorite = user.add_favorite(item)

        return generate_response(favorite.json(), CREATED) class FavoriteHandler(Resource):
    @auth.login_required
    def delete(self, favorite_id):
        user = auth.current_user
        try:
            favorite = Favorite.get(Favorite.uuid == favorite_id)
        except Favorite.DoesNotExist:
            return {"message": "Favorite {} doesn't exist.".format(favorite_id)}, NOT_FOUND

        if favorite.user != auth.current_user:
            return {'message': 'Favorite `{}` not found'.format(favorite_id)}, NOT_FOUND

        user.delete_favorite(favorite)

        return {'message': 'Favorite `{}` deleted.'.format(favorite_id)}, OK
import os
import utils  # flake8: noqa
from flask import Flask
from flask_restful import Api
from flask_cors import CORS
from auth import auth
from models import database
from views.address import AddressesHandler, AddressHandler
from views.auth import LoginHandler, LogoutHandler
from views.orders import OrdersHandler, OrderHandler
from views.items import ItemHandler, ItemsHandler, SearchItemHandler
from views.user import UsersHandler, UserHandler
from views.pictures import PictureHandler, ItemPictureHandler
from views.favorites import FavoritesHandler, FavoriteHandler
"""
Application entry point.

Creates the Flask application and flask-restful Resources using our :mod:`views`
modules.

"""




app = Flask(__name__)
CORS(
    app,
    resources={r"/*": {"origins": "*"}},
    supports_credentials=True,
)
api = Api(app)

auth.init_app(app)
app.secret_key = os.getenv(
    'SECRET_KEY',
    'development_secret_key',
)


@app.before_request
def database_connect():
    if database.is_closed():
        database.connect()


@app.teardown_request
def database_disconnect(response):
    if not database.is_closed():
        database.close()
    return response


api.add_resource(AddressesHandler, "/addresses/")
api.add_resource(AddressHandler, "/addresses/<uuid:address_uuid>")
api.add_resource(LoginHandler, "/auth/login/")
api.add_resource(LogoutHandler, "/auth/logout/")
api.add_resource(ItemsHandler, "/items/")

api.add_resource(ItemHandler, "/items/<uuid:item_uuid>")