class UserView:
    user_app = Blueprint('user_app', __name__, url_prefix='/user')

    @user_app.route("", methods=["POST"], endpoint='sign_up')
    @validate_params(
        Param('full_name', JSON, str, required=True),
        Param('email', JSON, str, required=True,
              rules=[Pattern(r'^[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*@[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*.[a-zA-Z]{2,3}$')]),
        Param('password', JSON, str,
              rules=[MaxLength(80)]),
        Param('password', JSON, str,
              rules=[MinLength(4)]),
        Param('auth_type_id', JSON, int)
    )
    def sign_up(*args):
        if args[4] not in range(1, 3):
            return jsonify({'message': 'INVALID_AUTH_TYPE'}), 400

        user_info = {
            'full_name': args[0],
            'email': args[1],
            'password': args[3],
            'auth_type_id': args[4]
        }
        user_service = UserService()
        result = user_service.sigh_up(user_info)
        return result

    @user_app.route("/sign-in", methods=["POST"], endpoint='sign_in')
    @validate_params(
        Param('email', JSON, str, required=True,
              rules=[Pattern(r'^[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*@[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*.[a-zA-Z]{2,3}$')]),
        Param('password', JSON, str,
              rules=[MaxLength(80)]),
        Param('password', JSON, str,
              rules=[MinLength(4)])
    )
    def sign_in(*args):
        user_info = {
            'email': args[0],
            'password': args[2]
        }
        user_service = UserService()
        result = user_service.sign_in(user_info)
        return result

    @user_app.route("/log-out", methods=["POST"], endpoint='log_out')
    @validate_params(
        Param('key', JSON, str)
    )
    def log_out(*args):
        redis_key = args[0]
        user_service = UserService()
        result = user_service.log_out(redis_key)
        return result
class ProductCreateGetSellerListView(MethodView):
    def __init__(self, service, database):
        self.service = service
        self.database = database

    @signin_decorator()
    @validate_params(
        Param('seller_name', GET, str, required=True, rules=[MaxLength(20)]), )
    def get(self, *args):
        try:
            data = {
                'seller_name': request.args.get('seller_name'),
                'permission_type_id': g.permission_type_id
            }

            connection = get_connection(self.database)

            sellers = dict()

            if data['seller_name'] and g.permission_type_id == 1:
                sellers = self.service.search_seller_list_service(
                    connection, data)

            return jsonify({'message': 'success', 'result': sellers})

        except Exception as e:
            traceback.print_exc()
            raise e

        finally:
            try:
                if connection:
                    connection.close()
            except Exception:
                raise DatabaseCloseFail('database close fail')
class ProductManageDetailView(MethodView):
    """ Presentation Layer

        Attributes:
            service  : ProductManageDetailView 클래스
            database : app.config['DB']에 담겨있는 정보(데이터베이스 관련 정보)

        Author: 심원두

        History:
            2021-01-02(심원두): 초기 작성
    """
    def __init__(self, service, database):
        self.service = service
        self.database = database

    @signin_decorator()
    @validate_params(
        Param('product_code',
              PATH,
              str,
              required=True,
              rules=[NotEmpty(), MaxLength(20)]), )
    def get(self, *args):
        """GET 메소드: 상품 코드에 해당하는 상품 정보를 불러온다.

            Args:
                'product_code' : 상품 코드
    
            Author: 심원두

            Returns:
                return {"message": "success", "result": result}
            
            Raises:
                500, {'message': 'product does not exist',
                      'errorMessage': 'product_does_not_exist'} : 상품 정보 취득 실패
                      
                500, {'message': 'product image not exist',
                      'errorMessage': 'product_image_not_exist'}: 상품 이미지 정보 취득 실패
                      
                500, {'message': 'stock info not exist',
                      'errorMessage': 'stock_does_not_exist'}: 옵션 정보 취득 실패
            
            History:
                2021-01-02(심원두): 초기 작성
        """

        try:
            data = {'product_code': request.view_args['product_code']}

            connection = get_connection(self.database)
            result = self.service.detail_product_service(connection, data)

            return jsonify({'message': 'success', 'result': result})

        except Exception as e:
            raise e

        finally:
            try:
                if connection:
                    connection.close()
            except Exception:
                raise DatabaseCloseFail('database close fail')
class ProductManageSearchView(MethodView):
    """ Presentation Layer

        Attributes:
            service  : MainCategoryListService 클래스
            database : app.config['DB']에 담겨있는 정보(데이터베이스 관련 정보)

        Author: 심원두

        History:
            2020-12-31(심원두): 초기 작성
            2021-01-03(심원두): 상품 리스트 검색 기능 구현
    """
    def __init__(self, service, database):
        self.service = service
        self.database = database

    @signin_decorator()
    @validate_params(
        Param('lookup_start_date',
              GET,
              str,
              required=False,
              rules=[DateRule(), NotEmpty()]),
        Param('lookup_end_date',
              GET,
              str,
              required=False,
              rules=[DateRule(), NotEmpty()]),
        Param('seller_name',
              GET,
              str,
              required=False,
              rules=[DefaultRule(), NotEmpty(),
                     MaxLength(20)]),
        Param('product_name',
              GET,
              str,
              required=False,
              rules=[DefaultRule(), NotEmpty(),
                     MaxLength(100)]),
        Param('product_id',
              GET,
              str,
              required=False,
              rules=[NumberRule(), NotEmpty()]),
        Param('product_code',
              GET,
              str,
              required=False,
              rules=[DefaultRule(), NotEmpty(),
                     MaxLength(20)]),
        Param('is_sale', GET, int, required=False, rules=[Enum(1, 2)]),
        Param('is_display', GET, int, required=False, rules=[Enum(1, 2)]),
        Param('is_discount', GET, int, required=False, rules=[Enum(1, 2)]),
        Param('page_number', GET, int, required=True, rules=[PageRule()]),
        Param('limit', GET, int, required=True, rules=[Enum(10, 20, 50)]))
    def get(self, *args):
        """GET 메소드: 특정 조건에 해당하는 상품 리스트를 조회한다.
            
            Args:
                'lookup_start_date'       : 조회 시작 기간
                'lookup_end_date'         : 조회 종료 기간
                'seller_name'             : 셀러명
                'product_name'            : 상품명
                'product_id'              : 상품 아이디
                'product_code'            : 상품 코드
                'seller_attribute_type_id : 셀러 속성
                'is_sale'                 : 할인 여부
                'is_display'              : 진열 여부
                'is_discount'             : 할인 여부
                'page_number'             : 페이지 번호
                'limit'                   : 한 화면에 보여줄 상품의 갯수
                
            Author: 심원두
            
            Returns:
                return {"message": "success", "result": result}
            
            Raises:
                400, {'message': 'key error',
                      'errorMessage': 'key_error' + format(e)} : 잘못 입력된 키값
                      
                400, {'message': 'both date field required',
                      'errorMessage': 'both_date_field_required'}: 필수 값 유효성 체크 에러
                      
                400, {'message': 'start date is greater than end date',
                      'errorMessage': 'start_date_is_greater_than_end_date'}: 날짜 비교 유효성 체크 에러
                
                400, {'message': 'invalid seller attribute type',
                      'errorMessage': 'invalid_seller_attribute_type'}: 셀러 타입 유효성 체크 에러
            
            History:
                2020-12-31(심원두): 초기생성
                2021-01-03(심원두): 상품 리스트 검색 기능 구현, Login Decorator 구현 예정
        """

        try:
            search_condition = {
                'seller_id':
                g.account_id if g.permission_type_id == 2 else None,
                'lookup_start_date':
                request.args.get('lookup_start_date', None),
                'lookup_end_date':
                request.args.get('lookup_end_date', None),
                'seller_name':
                request.args.get('seller_name', None),
                'product_name':
                request.args.get('product_name', None),
                'product_id':
                request.args.get('product_id', None),
                'product_code':
                request.args.get('product_code', None),
                'seller_attribute_type_ids':
                json.loads(request.args.get('seller_attribute_type_id'))
                if request.args.get('seller_attribute_type_id') else None,
                'is_sale':
                request.args.get('is_sale', None),
                'is_display':
                request.args.get('is_display', None),
                'is_discount':
                request.args.get('is_discount', None),
                'page_number':
                request.args.get('page_number'),
                'limit':
                request.args.get('limit')
            }

            search_condition_back_to_front = {
                'lookup_start_date':
                search_condition['lookup_start_date'],
                'lookup_end_date':
                search_condition['lookup_end_date'],
                'seller_name':
                search_condition['seller_name'],
                'product_name':
                search_condition['product_name'],
                'product_id':
                search_condition['product_id'],
                'product_code':
                search_condition['product_code'],
                'seller_attribute_type':
                search_condition['seller_attribute_type_ids'],
                'is_sale':
                0 if search_condition['is_sale'] is None else int(
                    search_condition['is_sale']),
                'is_display':
                0 if search_condition['is_display'] is None else int(
                    search_condition['is_display']),
                'is_discount':
                0 if search_condition['is_discount'] is None else int(
                    search_condition['is_discount']),
                'page_number':
                int(search_condition['page_number']),
                'limit':
                int(search_condition['limit'])
            }

            connection = get_connection(self.database)
            result = self.service.search_product_service(
                connection, search_condition)
            result['search_condition'] = search_condition_back_to_front

            return jsonify({'message': 'success', 'result': result})

        except KeyError as e:
            traceback.print_exc()
            raise e

        except Exception as e:
            traceback.print_exc()
            raise e

        finally:
            try:
                if connection:
                    connection.close()
            except Exception:
                raise DatabaseCloseFail('database close fail')
Example #5
0
@app.route('/api/login', methods=['POST'])
@validate_params(
    Param('login', JSON, str, rules=[Pattern(r'^[a-z_0-9\-.]{5,64}$')]),
    Param('password', JSON, str, rules=[Pattern(r'^[a-z_0-9\-.!$&]{5,15}$')])
)
def login(login, password):
    user = User.query.filter_by(login=login).first()
    if user is None or not user.is_password_valid(password):
        return {"error": "Invalid user name or password"}, 401
    user.update_last_login()
    return {"access_token": create_jwt(identity=user.id)}

@app.route('/api/posts', methods=['POST'])
@jwt_required
@validate_params(
    Param('text', JSON, str, rules=[MaxLength(255)]),
)
def create_post(text):
    user = get_JWT_user()
    user.update_last_activity()
    post = Post(body=text, author=user)
    db.session.add(post)
    try:
        db.session.commit()
    except Exception:
        return try_later()
    return {"post": {"id": post.id}}, 201

@app.route('/api/posts/<int:id>/like', methods=['GET', 'POST'])
@jwt_required
def like(id):
class AccountView:
    account_app = Blueprint('account_app', __name__, url_prefix='/account')

    @account_app.route('signup/master', methods=['POST'])
    @validate_params(
        Param('email', JSON, str,
              rules=[Pattern(r'^[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*@[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*.[a-zA-Z]{2,3}$')]),
        Param('password', JSON, str,
              rules=[MaxLength(80), MinLength(4)]),
        Param('account_type_id', JSON, int),
        Param('account_type_id', JSON, str,
              rules=[Pattern(r"^[1-2]{1}$")]),
        Param('name', JSON, str, rules=[Pattern(r"^[가-힣]{1,20}$")]),
        Param('master_code', JSON, str, required=True)
    )
    def sign_up_master(*args):
        connection = None
        account_info = {
            'email': args[0],
            'password': args[1],
            'account_type_id': args[2],
            'name': args[4],
            'master_code': args[5]
        }

        connection = connect_db()
        if connection:
            account_service = AccountService()
            try:
                account_service.signup_account(account_info, connection)
                if account_info['master_code'] != 'brandi_team1':
                    connection.rollback()
                    return jsonify({'MESSAGE': 'WRONG_MASTER_CODE'})
                connection.commit()
                return jsonify({'MESSAGE': 'ACCOUNT_CREATED', }), 200
            except Exception as e:
                connection.rollback()
                return jsonify({'MESSAGE': f'{e}'}), 400
            finally:
                if connection:
                    connection.close()

    @account_app.route('signup/seller', methods=['POST'])
    @validate_params(
        Param('email', JSON, str,
              rules=[Pattern(r'^[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*@[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*.[a-zA-Z]{2,3}$')]),
        Param('password', JSON, str, rules=[MaxLength(80), MinLength(4)]),
        Param('account_type_id', JSON, int),
        Param('account_type_id', JSON, str, rules=[Pattern(r"^[1-2]{1}$")]),
        Param('service_number', JSON, str, rules=[Pattern(r"^[0-9]{11}$|^[0-9]{12}$")]),
        Param('seller_name_kr', JSON, str, rules=[Pattern(r"^[가-힣0-9]{1,20}$")]),
        Param('seller_name_en', JSON, str, rules=[Pattern(
            r"^[a-zA-Z0-9àáâäãåąčćęèéêëėįìíîïłńòóôöõøùúûüųūÿýżźñçčšžÀÁÂÄÃÅĄĆČĖĘÈÉÊËÌÍÎÏĮŁŃÒÓÔÖÕØÙÚÛÜŲŪŸÝŻŹÑßÇŒÆČŠŽ∂ð ,.'-]{1,20}$")]),
        Param('subcategory_id', JSON, str, rules=[Pattern(r"^[1-8]{1}$")])
    )
    def sign_up_seller(*args):
        connection = None
        try:
            account_info = {
                'email': args[0],
                'password': args[1],
                'account_type_id': args[2],
                'name': '미설정',
                'service_number': args[4],
                'seller_name_kr': args[5],
                'seller_name_en': args[6],
                'subcategory_id': args[7],
                'seller_status_id': 1
            }
            print(account_info)
            connection = connect_db()
            account_service = AccountService()
            account_service.signup_seller(account_info, connection)
            connection.commit()

            return jsonify({'MESSAGE': 'SUCCESS'}), 200
        except Exception as e:
            connection.rollback()
            return jsonify({'MESSAGE': f'{e}'}), 400
        finally:
            if connection:
                connection.close()

    @account_app.route('/signin', methods=['POST'])
    def sign_in():
        connection = None
        try:
            connection = connect_db()
            login_data = request.json
            account_service = AccountService()
            token = account_service.signin(login_data, connection)
            return token
        except Exception as e:
            return jsonify({'MESSAGE': f'{e}'}), 400
        finally:
            if connection:
                connection.close()


    @account_app.route('/seller_list', methods=['GET'])
    @login_validator
    @validate_params(
        Param('seller_id', GET, int, required=False),
        Param('account_id', GET, int, required=False),
        Param('email', GET, str, required=False),
        Param('seller_en', GET, str, required=False),
        Param('seller_kr', GET, str, required=False),
        Param('user_id', GET, int, required=False),
        Param('manager_name', GET, str, required=False),
        Param('manager_email', GET, str, required=False),
        Param('seller_status', GET, str, required=False),
        Param('manager_phone', GET, str, required=False),
        Param('seller_category', GET, str, required=False),
        Param('created_lower', GET, str, required=False),
        Param('created_upper', GET, str, required=False),
        Param('excel', GET, int, required=False),
        Param('page', GET, int, required=False),
        Param('limit', GET, int, required=False),
        Param('order_by', GET, str, required=False, rules=[Enum('asc', 'desc')])
    )
    def list_sellers(*args):
        db_connection = None
        user = g.token_info
        filter_info = {
            'seller_id': args[0],
            'account_id': args[1],
            'email': args[2],
            'seller_en': args[3],
            'seller_kr': args[4],
            'user_id': args[5],
            'manager_name': args[6],
            'manager_email': args[7],
            'seller_status': args[8],
            'manager_phone': args[9],
            'seller_category': args[10],
            'created_upper': args[11],
            'created_lower': args[12],
            'excel': args[13],
            'page': args[14] if args[14] else 1,
            'limit': args[15] if args[15] else 10,
            'order_by': args[16]
        }
        try:
            connection = connect_db()
            account_service = AccountService()
            seller_list = account_service.filter_seller(filter_info, user, connection)
            return jsonify({'seller_list': seller_list}), 200

        except Exception as e:
            return jsonify({'MESSAGE': f'{e}'}), 400
        finally:
            connection.close()

    @account_app.route('edit', methods=['PATCH'])
    @login_validator
    @validate_params(
        Param('email', JSON, str,
              rules=[Pattern(r'^[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*@[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*.[a-zA-Z]{2,3}$')], required=False),
        Param('password', JSON, str,
              rules=[MaxLength(80), MinLength(4)], required=False),
        Param('account_type_id', JSON, int, required=False),
        Param('account_type_id', JSON, str,
              rules=[Pattern(r"^[1-2]{1}$")], required=False),
        Param('name', JSON, str, rules=[Pattern(r"^[가-힣]{1,20}$")], required=False),
        Param('account_id', JSON, int, required=True),
        Param('is_active', JSON, int, required=False)
    )
    def edit_account(*args):
        connection = None
        change_info = {
            'email': args[0],
            'password': args[1],
            'account_type_id': args[2],
            'name': args[4],
            'id': args[5],
            'is_active': args[6]
        }

        connection = connect_db()
        user = g.token_info
        if connection:
            account_service = AccountService()
            try:
                change_account = account_service.change_account_info(change_info, user, connection)
                connection.commit()
                return change_account
            except Exception as e:
                connection.rollback()
                return jsonify({'MESSAGE': f'{e}'}), 400
            finally:
                connection.close()

    @account_app.route('edit_seller', methods=['PATCH'])
    @login_validator
    @validate_params(
        Param('subcategory_id', JSON, int, required=False),
        Param('seller_status_id', JSON, int, required=False),
        Param('seller_name_kr', JSON, str, rules=[Pattern(r"^[가-힣0-9]{1,20}$")], required=False),
        Param('seller_name_en', JSON, str, rules=[Pattern(
            r"^[a-zA-Z0-9àáâäãåąčćęèéêëėįìíîïłńòóôöõøùúûüųūÿýżźñçčšžÀÁÂÄÃÅĄĆČĖĘÈÉÊËÌÍÎÏĮŁŃÒÓÔÖÕØÙÚÛÜŲŪŸÝŻŹÑßÇŒÆČŠŽ∂ð ,.'-]{1,20}$")], required= False),
        Param('seller_number', JSON, str, rules=[Pattern(r"^[0-9]{11}$|^[0-9]{12}$")], required=False),
        Param('profile_pic_url', JSON, str, required=False),
        Param('short_desc', JSON, str, required=False),
        Param('long_desc', JSON, str, required=False),
        Param('open_time', JSON, str, rules=[Pattern('^(20)[\d]{2}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[0-1])$')], required= False),
        Param('close_time', JSON, str, rules=[Pattern('^(20)[\d]{2}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[0-1])$')], required= False),
        Param('delivery_policy', JSON, str, required=False),
        Param('return_policy', JSON, str, required=False),
        Param('zip_code', JSON, int, required=False),
        Param('address_1', JSON, str, required=False),
        Param('address_2', JSON, str, required=False),
        Param('is_open_weekend', JSON, int, required=False),
        Param('seller_id', JSON, int, required=True)
    )
    def edit_seller(*args):
        connection = None
        change_info = {
            'subcategory_id': args[0],
            'seller_status_id': args[1],
            'seller_name_kr': args[2],
            'seller_name_en': args[3],
            'seller_number': args[4],
            'profile_pic_url': args[5],
            'short_desc': args[6],
            'long_desc': args[7],
            'open_time': args[8],
            'close_time': args[9],
            'delivery_policy': args[10],
            'return_policy': args[11],
            'zip_code': args[12],
            'address_1': args[13],
            'address_2': args[14],
            'is_open_weekend': args[15],
            'seller_id': args[16]
        }
        connection = connect_db()
        user = g.token_info
        if connection:
            account_service = AccountService()
            try:
                change_account = account_service.change_seller_info(change_info, user, connection)
                connection.commit()
                return change_account
            except Exception as e:
                connection.rollback()
                return jsonify({'MESSAGE': f'{e}'}), 400
            finally:
                connection.close()


    @account_app.route('change_seller_status', methods=['PATCH'])
    @login_validator
    @validate_params(
        Param('action_id', JSON, str),
        Param('status_id', JSON, str),
        Param('seller_id', JSON, str)
    )
    def seller_actions(*args):
        connection = None
        status = {
            'action_id': args[0],
            'status_id': args[1],
            'seller_id': args[2]
        }
        user = g.token_info
        connection = connect_db()
        if connection:
            try:
                account_service = AccountService()
                account_service.change_status(status, user, connection)
                connection.commit()
                return jsonify({'status_actions': 'SUCCESS'}), 200
            except Exception as e:
                connection.rollback()
                return jsonify({'MESSAGE': f'{e}'}), 400
            finally:
                connection.close()
class CreateProductView(MethodView):
    """ Presentation Layer

        Attributes:
            service  : CreateProductService 클래스
            database : app.config['DB']에 담겨있는 정보(데이터베이스 관련 정보)

        Author: 심원두

        History:
            2020-12-29(심원두): 초기 생성. products insert, product_code updated, product_histories 생성 기능 작성
            2020-12-30(심원두): 각 Param rules 추가, stock insert 기능 작성.
            2020-01-03(심원두): 상품 등록 Param rules 추가
    """
    def __init__(self, service, database):
        self.service = service
        self.database = database

    @signin_decorator()
    @validate_params(
        Param('seller_name', GET, str, required=False, rules=[MaxLength(20)]),
        Param('main_category_id',
              GET,
              str,
              required=False,
              rules=[NumberRule()]))
    def get(self, *args):
        """POST 메소드: 상품 정보 등록 초기 화면

            Args:
                'seller_name'      : 사용자가 입력한 셀러명
                'main_category_id' : 사용자가 선택한 메인 카테고리 아이디

            Author: 심원두

            Returns:
                return {"message": "success", "result": [{}]}

            Raises:
                400, {'message': 'key error',
                      'errorMessage': 'key_error' + format(e)}: 잘못 입력된 키값

                500, {'message': 'fail to get sub category list',
                      'errorMessage': 'fail_to_get_sub_category_list'}: 색상 정보 취득 실패

                500, {'message': 'fail to get product origin types',
                      'errorMessage': 'fail_to_get_product_origin_types'} : 원산지 정보 취득 실패

                500, {'message': 'fail to get color list',
                      'errorMessage': 'fail_to_get_color_list'}: 색상 정보 취득 실패

                500, {'message': 'fail to get color list',
                      'errorMessage': 'fail_to_get_color_list'}: 색상 정보 취득 실패

            History:
                2020-12-30(심원두): 초기생성
                2021-01-06(심원두): 로그인 데코레이터 처리 추가. 관리자일 경우에만 셀러 검색 허용하도록 수정
        """

        try:
            data = {
                'seller_name': request.args.get('seller_name', None),
                'main_category_id': request.args.get('main_category_id', None)
            }

            connection = get_connection(self.database)

            if data['seller_name'] and g.permission_type_id == 1:
                sellers = self.service.search_seller_list_service(
                    connection, data)

                return jsonify({'message': 'success', 'result': sellers})

            if data['main_category_id']:
                sub_categories = self.service.get_sub_category_list_service(
                    connection, data)

                return jsonify({
                    'message': 'success',
                    'result': sub_categories
                })

            result = dict()

            result['product_origin_types'] = \
                self.service.get_product_origin_types_service(
                    connection
                )

            result['color_list'] = \
                self.service.get_color_list_service(
                    connection
                )

            result['size_list'] = \
                self.service.get_size_list_service(
                    connection
                )

            return jsonify({'message': 'success', 'result': result})

        except KeyError as e:
            traceback.print_exc()
            raise e

        except Exception as e:
            traceback.print_exc()
            raise e

        finally:
            try:
                if connection:
                    connection.close()
            except Exception:
                raise DatabaseCloseFail('database close fail')

    @signin_decorator()
    @validate_params(
        Param('seller_id', FORM, str, required=True, rules=[NumberRule()]),
        Param('is_sale', FORM, int, required=True, rules=[Enum(0, 1)]),
        Param('is_display', FORM, int, required=True, rules=[Enum(0, 1)]),
        Param('main_category_id',
              FORM,
              str,
              required=True,
              rules=[NumberRule()]),
        Param('sub_category_id',
              FORM,
              str,
              required=True,
              rules=[NumberRule()]),
        Param('is_product_notice',
              FORM,
              int,
              required=True,
              rules=[Enum(0, 1)]),
        Param('manufacturer', FORM, str, required=False,
              rules=[MaxLength(30)]),
        Param('manufacturing_date', FORM, str, required=False),
        Param('product_origin_type_id', FORM, str, required=False),
        Param('product_name',
              FORM,
              str,
              required=True,
              rules=[NotEmpty(), MaxLength(100)]),
        Param('description', FORM, str, required=False,
              rules=[MaxLength(200)]),
        Param('detail_information',
              FORM,
              str,
              required=True,
              rules=[NotEmpty()]), Param('options', FORM, list, required=True),
        Param('minimum_quantity',
              FORM,
              str,
              required=False,
              rules=[NumberRule()]),
        Param('maximum_quantity',
              FORM,
              str,
              required=False,
              rules=[NumberRule()]),
        Param('origin_price', FORM, str, required=True, rules=[NumberRule()]),
        Param('discount_rate', FORM, str, required=True, rules=[NumberRule()]),
        Param('discounted_price',
              FORM,
              str,
              required=True,
              rules=[NumberRule()]),
        Param('discount_start_date', FORM, str, required=False),
        Param('discount_end_date', FORM, str, required=False))
    def post(self, *args):
        """ POST 메소드: 상품 정보 등록

            Args:
            - 사용자 입력 값(상품 이미지 최대 5개) : image_files
            - 사용자 입력 값(옵션 정보 리스트)    : options
            - 사용자 입력 값
            Form-Data: (
                'seller_id'
                'account_id',
                'is_sale',
                'is_display',
                'main_category_id',
                'sub_category_id',
                'is_product_notice',
                'manufacturer',
                'manufacturing_date',
                'product_origin_type_id',
                'product_name',
                'description',
                'detail_information',
                'options',
                'minimum_quantity',
                'maximum_quantity',
                'origin_price',
                'discount_rate',
                'discounted_price',
                'discount_start_date',
                'discount_end_date',
            )

            Author: 심원두

            Returns:
                200, {'message': 'success'}                                                   : 상품 정보 등록 성공

            Raises:
                400, {'message': 'key_error',
                      'errorMessage': 'key_error_' + format(e)}                               : 잘못 입력된 키값

                400, {'message': 'required field is blank',
                      'errorMessage': 'required_manufacture_information'}                     : 제조 정보 필드 없음

                400, {'message': 'required field is blank',
                      'errorMessage': 'required_discount_start_or_end_date'}                  : 필수 입력 항목 없음

                400, {'message': 'compare quantity field check error',
                      'errorMessage': 'minimum_quantity_cannot_greater_than_maximum_quantity'}: 최소 구매 수량이 최대 보다 큼

                400, {'message': 'compare price field check error',
                      'errorMessage': 'discounted_price_cannot_greater_than_origin_price'}    : 할인가가 판매가 보다 큼

                400, {'message': 'compare price field check error',
                      'errorMessage': 'wrong_discounted_price'}                               : 판매가와 할인가 일치하지 않음

                400, {'message': 'compare price field check error',
                      'errorMessage': 'required_discount_start_or_end_date'}                  : 할인 시작, 종료 일자 필드 없음

                400, {'message': 'start date is greater than end date',
                      'errorMessage': 'start_date_cannot_greater_than_end_date'}              : 할인 시작일이 종료일 보다 큼

                400, {'message': 'compare price field check error',
                      'errorMessage': 'discounted_price_have_to_same_with_origin_price'}      : 할인가, 판매가 불일치(할인율 0)

                413, {'message': 'invalid file',
                      'errorMessage': 'invalid_file'}                                         : 파일 이름이 공백, 혹은 파일을 정상적으로 받지 못함

                413, {'message': 'file size too large',
                      'errorMessage': 'file_size_too_large'}                                  : 파일 사이즈 정책 위반 (4메가 이상인 경우)

                413, {'message': 'file scale too small, 640 * 720 at least',
                      'errorMessage': 'file_scale_at_least_640*720'}                          : 파일 스케일 정책 위반 (680*720 미만인 경우)

                413, {'message': 'only allowed jpg type',
                      'errorMessage': 'only_allowed_jpg_type'}                                : 파일 확장자 정책 위반 (JPG, JPEG 아닌 경우)

                500, {'message': 'image_file_upload_to_amazon_fail',
                      'errorMessage': 'image_file_upload_fail'}                               : 이미지 업로드 실패

                500, {'message': 'product create denied',
                      'errorMessage': 'unable_to_create_product'}                             : 상품 정보 등록 실패

                500, {'message': 'product code update denied',
                      'errorMessage': 'unable_to_update_product_code'}                        : 상품 코드 갱신 실패

                500, {'message': 'product code update denied',
                      'errorMessage': 'unable_to_update_product_code'}                        : 상품 코드 갱신 실패

                500, {'message': 'product image create denied',
                      'errorMessage': 'unable_to_create_product_image'}                       : 상품 이미지 등록 실패

                500, {'message': 'stock create denied',
                      'errorMessage': 'unable_to_create_stocks'}                              : 상품 옵션 정보 등록 실패

                500, {'message': 'product history create denied',
                      'errorMessage': 'unable_to_create_product_history'}                     : 상품 이력 등록 실패

                500, {'message': 'bookmark volumes create denied',
                      'errorMessage': 'unable_to_create_bookmark_volumes'}                    : 북마크 초기 등록 실패

                500, {'message': 'database_connection_fail',
                      'errorMessage': 'database_close_fail'}                                  : 커넥션 종료 실패

                500, {'message': 'database_error',
                      'errorMessage': 'database_error_' + format(e)}                          : 데이터베이스 에러

                500, {'message': 'internal_server_error',
                      'errorMessage': format(e)})                                             : 서버 에러

            History:
                2020-12-29(심원두): 초기 생성
                2021-01-03(심원두): 파라미터 유효성 검사 추가 Enum(), NotEmpty()
                2021-01-05(심원두): -이미지 저장 처리 순서를 3번째에서 가장 마지막으로 내림. 테이블 인서트 처리에 문제가 있을 경우,
                                    S3에 올라간 이미지는 롤백을 할 수 없는 이슈 반영.
                                   -북마크 테이블 초기 등록 처리 추가.
        """

        try:
            data = {
                'seller_id':
                request.form.get('seller_id'),
                'account_id':
                g.account_id,
                'is_sale':
                request.form.get('is_sale'),
                'is_display':
                request.form.get('is_display'),
                'main_category_id':
                request.form.get('main_category_id'),
                'sub_category_id':
                request.form.get('sub_category_id'),
                'is_product_notice':
                request.form.get('is_product_notice'),
                'manufacturer':
                request.form.get('manufacturer'),
                'manufacturing_date':
                request.form.get('manufacturing_date'),
                'product_origin_type_id':
                request.form.get('product_origin_type_id'),
                'product_name':
                request.form.get('product_name'),
                'description':
                request.form.get('description'),
                'detail_information':
                request.form.get('detail_information'),
                'minimum_quantity':
                request.form.get('minimum_quantity'),
                'maximum_quantity':
                request.form.get('maximum_quantity'),
                'origin_price':
                request.form.get('origin_price'),
                'discount_rate':
                request.form.get('discount_rate'),
                'discounted_price':
                request.form.get('discounted_price'),
                'discount_start_date':
                request.form.get('discount_start_date'),
                'discount_end_date':
                request.form.get('discount_end_date')
            }

            product_images = request.files.getlist("image_files")
            stocks = json.loads(request.form.get('options'))
            connection = get_connection(self.database)

            product_id = self.service.create_product_service(connection, data)

            product_code = self.service.update_product_code_service(
                connection, product_id)

            self.service.create_stock_service(connection, product_id, stocks)

            self.service.create_product_history_service(
                connection, product_id, data)

            self.service.create_product_sales_volumes_service(
                connection, product_id)

            self.service.create_bookmark_volumes_service(
                connection, product_id)

            self.service.create_product_images_service(connection,
                                                       data['seller_id'],
                                                       product_id,
                                                       product_code,
                                                       product_images)

            connection.commit()

            return jsonify({'message': 'success'}), 200

        except KeyError as e:
            traceback.print_exc()
            connection.rollback()
            raise e

        except Exception as e:
            traceback.print_exc()
            connection.rollback()
            raise e

        finally:
            try:
                if connection:
                    connection.close()
            except Exception:
                traceback.print_exc()
                raise DatabaseCloseFail('database close fail')
class ProductRegistView(MethodView):
    """ Presentation Layer

        Attributes:
            service  : ProductManageService 클래스
            database : app.config['DB']에 담겨있는 정보(데이터베이스 관련 정보)

        Author: 심원두

        History:
            2021-01-13(심원두): refactoring 초기 작성
            2021-01-15(심원두):
                상품 등록 초기 화면 기능 작성 완료. 권한 타입 상수 생성. 권한에 따른 분기 처리 완료.
                파라미터 룰 설정 완료
    """
    def __init__(self, service, database):
        self.service = service
        self.database = database

    @signin_decorator()
    @validate_params(
        Param('seller_name',
              GET,
              str,
              required=False,
              rules=[NotEmpty(), MaxLength(20)]),
        Param('seller_id',
              GET,
              str,
              required=False,
              rules=[NotEmpty(), NumberRule()]),
        Param('main_category_id',
              GET,
              str,
              required=False,
              rules=[NotEmpty(), NumberRule()]),
    )
    def get(self, *args):
        """ GET 메소드: 상품 등록 초기 화면
            
            Args:
                'seller_name'      : 셀러명
                'seller_id'        : 셀러 아이디
                'main_category_id' : 메인 카테고리 아이디
            
            Author: 심원두
            
            Returns:
                result - 아래의 조건에 따라 분기 처리
                    1. 관리자 권한 초기 화면            - 원산지, 색상, 사이즈 정보 출력
                    2. 관리자 권한 셀러 검색            - 셀러 정보 리스트 출력
                    3. 관리자 권한 셀러 선택            - 메인 카테고리 리스트 출력
                    4. 관리자&셀러 권한 메인 카테고리 선택 - 서브 카테고리 출력
                    5. 셀러 권한 초기화면               - 원산지, 색상, 사이즈, 메인 카테고리 정보 출력
                    
            Raises:
                500, {'message': 'fail to get main category list',
                      'errorMessage': 'fail_to_get_main_category_list'}: 메인 카테고리 정보 취득 실패
                
                500, {'message': 'fail to get main category list',
                      'errorMessage': 'fail_to_get_main_category_list'}: 메인 카테고리 정보 취득 실패
            
            History:
                2020-12-30(심원두): 초기생성
                2021-01-15(심원두): 리펙토링 완료
        """

        connection = None

        try:
            connection = get_connection(self.database)

            data = {
                'seller_name': request.args.get('seller_name'),
                'seller_id': request.args.get('seller_id'),
                'main_category_id': request.args.get('main_category_id'),
            }

            result = dict()

            if data['seller_name']:
                result['seller_list'] = self.service.\
                    get_seller_list_by_name_service(
                        connection,
                        data,
                        g.permission_type_id
                    )

                return jsonify({'message': 'success', 'result': result}), 200

            if data['seller_id']:
                result['main_category_list'] = self.service.\
                    get_main_category_list_service(
                        connection
                    )

                if g.permission_type_id is ACCOUNT_ADMIN:
                    return jsonify({
                        'message': 'success',
                        'result': result
                    }), 200

            if data['main_category_id']:
                result['sub_category_list'] = self.service. \
                    get_sub_category_list_service(
                        connection,
                        data
                    )

                return jsonify({'message': 'success', 'result': result}), 200

            result[
                'product_origin_types'] = self.service.get_product_origin_types_service(
                    connection)
            result['color_list'] = self.service.get_color_list_service(
                connection)
            result['size_list'] = self.service.get_size_list_service(
                connection)

            return jsonify({'message': 'success', 'result': result}), 200

        except KeyError as e:
            traceback.print_exc()
            raise e

        except Exception as e:
            traceback.print_exc()
            raise e

        finally:
            try:
                if connection:
                    connection.close()
            except Exception:
                raise DatabaseCloseFail('database close fail')
class SellerView:
    """ 셀러 뷰

    Authors:
        [email protected] (이소헌)

    History:
        2020-03-25 ([email protected]): 초기 생성

    """
    seller_app = Blueprint('seller_app', __name__, url_prefix='/seller')

    @seller_app.route('', methods=['POST'])
    @validate_params(
        Param('login_id',
              JSON,
              str,
              rules=[Pattern(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9_-]{4,19}')]),
        Param('password', JSON, str, rules=[MaxLength(80)]),
        Param('password', JSON, str, rules=[MinLength(4)]),
        Param('contact_number',
              JSON,
              str,
              rules=[Pattern(r'^[0-9]{3}-{1}[0-9]{4}-{1}[0-9]{4}$')]),
        Param('seller_type_id', JSON, int),
        Param('name_kr',
              JSON,
              str,
              rules=[Pattern(r'^[가-힣a-zA-Z0-9\ ]{1,45}$')]),
        Param('name_en', JSON, str, rules=[Pattern(r'^[a-z\ ]{1,45}$')]),
        Param('center_number',
              JSON,
              str,
              rules=[Pattern(r'^[0-9]{2,3}-{1}[0-9]{3,4}-{1}[0-9]{4}$')]),
        Param(
            'site_url',
            JSON,
            str,
            rules=[
                Pattern(
                    r"^(?:http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~:/?#[\]@!\$&'\(\)\*\+,;=.]+$"
                )
            ]), Param('site_url', JSON, str, rules=[MaxLength(200)]),
        Param('kakao_id',
              JSON,
              str,
              required=False,
              rules=[Pattern(r'^[가-힣a-zA-Z0-9]{1,45}$')]),
        Param('insta_id',
              JSON,
              str,
              required=False,
              rules=[Pattern(r'^[a-zA-Z0-9]{1,45}$')]),
        Param('seller_type_id', JSON, str, rules=[Pattern(r"^[1-7]{1}$")]))
    def sign_up(*args):
        """ 계정 회원가입 엔드포인트

        회원가입 엔드포인트 입니다.
        request.body 로 회원가입에 필요한 정보를 받고,
        유효성 검사를 마친 정보를 account_info 에 담아
        service 에 전달합니다.

        Args:
            *args: 유효성 검사를 통과한 파라미터

        request.body:
            login_id 로그인 아이디 str
            password 비밀번호 str
            contact_number 담당자 번호 str
            seller_type_id 셀러 속성 아이디 int
            name_kr 셀러명 str
            name_en 셀러 영문명 str
            center_number 고객센터 번호 str
            site_url 사이트 URL str
            kakao_id 카카오 아이디 str required=False
            insta_id 인스타 아이디 str required=False

        Returns: http 응답코드
            200: SUCCESS 셀러 회원가입 완료
            400: EXISTING_LOGIN_ID, EXISTING_NAME_KR,
                 EXISTING_NAME_EN, INVALID_KEY
            500: NO_DATABASE_CONNECTION

        Authors:
            [email protected] (이종민)

        History:
        2020-04-06 ([email protected]): 초기 생성
        2020-04-07 ([email protected]):
            'center_number'의 중간부분 유효성검사 허용 범위를 4글자->3~4글자로 변경

        """

        # validation 확인이 된 data 를 account_info 로 재정의
        account_info = {
            'login_id': args[0],
            'password': args[1],
            'contact_number': args[3],
            'seller_type_id': args[4],
            'name_kr': args[5],
            'name_en': args[6],
            'center_number': args[7],
            'site_url': args[8],
            'kakao_id': args[10],
            'insta_id': args[11]
        }

        # 데이터베이스 연결
        try:
            db_connection = get_db_connection()
            if db_connection:
                seller_service = SellerService()
                sign_up_result = seller_service.sign_up(
                    account_info, db_connection)
                return sign_up_result
            else:
                return jsonify({'message': 'NO_DATABASE_CONNECTION'}), 500

        except Exception as e:
            return jsonify({'message': f'{e}'}), 500

        finally:
            try:
                db_connection.close()
            except Exception as e:
                return jsonify({'message': f'{e}'}), 500

    @seller_app.route('/login', methods=['POST'])
    @validate_params(Param('login_id', JSON, str),
                     Param('password', JSON, str))
    def login(*args):
        """ 셀러 로그인 엔드포인트
        셀러 로그인 엔드포인트 입니다.
        login_id 와 password 를 받습니다.

        request.body:
            login_id: 로그인 아이디
            password: 로그인 비밀번호

        Args:
            *args: 유효성 검사를 통과한 request.body 의 인자

        Returns:
            200: SUCCESS 로그인 성공
            401: INVALID_PASSWORD, STATUS_1_CANT_LOGIN
            500: NO_DATABASE_CONNECTION

        Authors:
            [email protected] (최예지)

        History:
            2020-04-04 ([email protected]): 초기 생성
            2020-04-04 ([email protected]): account_info 에 필요한 정보 담음, DB 열림닫힘 여부에 따라 실행되는 함수 작성
        """

        # validation 확인이 된 data 를 account_info 로 재정의
        account_info = {'login_id': args[0], 'password': args[1]}

        try:
            # DB에 연결
            db_connection = get_db_connection()
            if db_connection:

                # service 에 있는 SellerService 를 가져와서 seller_service 라는 인스턴스를 만듦
                seller_service = SellerService()

                # 로그인 함수를 실행한 결과값을 login_result 에 저장
                login_result = seller_service.login(account_info,
                                                    db_connection)
                return login_result

            # DB가 열리지 않았을 경우
            else:
                return jsonify({'message': 'NO_DATABASE_CONNECTION'}), 500

        # 정의하지 않은 모든 error 를 잡아줌
        except Exception as e:
            return jsonify({'message': f'{e}'}), 500

        # try 랑 except 에 상관없이 무조건 실행
        finally:
            try:
                db_connection.close()
            except Exception as e:
                return jsonify({'message': f'{e}'}), 500

    @seller_app.route('', methods=['GET'], endpoint='get_all_sellers')
    @login_required
    @validate_params(Param('seller_account_no', GET, int, required=False),
                     Param('login_id', GET, str, required=False),
                     Param('name_en', GET, str, required=False),
                     Param('name_kr', GET, str, required=False),
                     Param('brandi_app_user_id', GET, int, required=False),
                     Param('manager_name', GET, str, required=False),
                     Param('manager_email', GET, str, required=False),
                     Param('seller_status', GET, str, required=False),
                     Param('manager_contact_number', GET, str, required=False),
                     Param('seller_type_name', GET, str, required=False),
                     Param('start_time', GET, str, required=False),
                     Param('close_time', GET, str, required=False),
                     Param('excel', GET, int, required=False),
                     Param('offset', GET, int, required=False),
                     Param('limit', GET, int, required=False))
    def get_seller_list(*args):
        """ 가입된 모든 셀러 정보 리스트를 표출
        유효성검사를 통과한 값을 request 에 넣어줌.

        Args:
            g.account_info: 데코레이터에서 넘겨받은 수정을 수행하는 계정 정보
            g.account_info.seller_account_no: 검색 셀러 번호
            g.account_info.account_no: 데코레이터에서 확인된 계정번호드
            args: path parameter 를 통해서 들어온 검색 키워

        Returns:
            seller_list_result: 가입된 모든 셀러 및 셀러 세부 정보 리스트로 표출(seller_service 에서 받은 리턴 값.)
            400: seller_service 로 값을 넘겨줄 때 애러가나면 400 리턴
            500: database 연결에 실패하면 500리턴

        Authors:
            [email protected] (윤희철)

        History:
            2020-04-03 ([email protected]): 초기 생성
            2020-04-07 ([email protected]): 파라미터 유효성검사 추가
            2020-04-10 ([email protected]): 애러 처리 추가
            2020-04-14 ([email protected]): offset 과 limit 도 유효성검사 실시
        """

        # 유효성 확인 위해 기간 데이터 먼저 정의
        start_time = args[10]
        close_time = args[11]

        # 두 값이 모두 들어왔을 때, 시작 기간이 종료 기간보다 늦으면 시작기간 = 종료기간
        if start_time and close_time:
            if start_time > close_time:
                start_time = close_time

        # validation 을 통과한 값을 담을 딕셔너리를 만들어줌.
        valid_param = {}

        # valid_param 딕셔너리에 validation 을 통과한 query parameter 을 넣어줌.
        valid_param['seller_account_no'] = args[0]
        valid_param['login_id'] = args[1]
        valid_param['name_en'] = args[2]
        valid_param['name_kr'] = args[3]
        valid_param['brandi_app_user_id'] = args[4]
        valid_param['manager_name'] = args[5]
        valid_param['manager_email'] = args[6]
        valid_param['seller_status'] = args[7]
        valid_param['manager_contact_number'] = args[8]
        valid_param['seller_type_name'] = args[9]
        valid_param['start_time'] = start_time
        valid_param['close_time'] = close_time
        valid_param['excel'] = args[12]
        valid_param['offset'] = args[13] if args[13] else 0
        valid_param['limit'] = args[14] if args[14] else 10

        # 유저 정보를 g에서 읽어와서 service 에 전달
        user = g.account_info

        # 데이터베이스 커넥션을 열어줌.
        try:
            db_connection = DatabaseConnection()
            if db_connection:
                seller_service = SellerService()
                seller_list_result = seller_service.get_seller_list(
                    valid_param, user, db_connection)
                return seller_list_result
            else:
                return jsonify({'message': 'NO_DATABASE_CONNECTION'}), 500

        except Exception as e:
            return jsonify({'message': f'{e}'}), 500

        finally:
            try:
                db_connection.close()
            except Exception as e:
                return jsonify({'message': f'{e}'}), 500

    @seller_app.route('/<int:parameter_account_no>',
                      methods=['GET'],
                      endpoint='get_seller_info')
    @login_required
    @validate_params(
        Param('parameter_account_no', PATH, int),
        Param('parameter_account_no', PATH, str, rules=[MaxLength(6)]),
    )
    def get_seller_info(*args):
        """ 계정 셀러정보 표출 엔드포인트

        셀러정보를 표출하는 엔드포인트 입니다.
        url 로 셀러정보를 확인하고 싶은 계정 번호를 받습니다.

        셀러정보를 열람을 수행하려는 계정의 번호를 데코레이터로부터 받습니다.
        열람 대상 셀러정보의 계정의 번호를 url parameter 로 받습니다.

        받은 정보를 유효성 검사를 거친 후 account_info 로 저장해 service 로 넘겨줍니다.

        Args:
            *args: 유효성 검사를 통과한 파라미터

        url parameter:
            parameter_account_no: 열람하고자 하는 셀러정보의 계정 번호

        g.account_info: 데코레이터에서 넘겨받은(셀러정보를 열람을 수행하려는) 계정 정보
            auth_type_id: 계정의 권한정보
            account_no: 데코레이터에서 확인된 계정번호

        Returns: http 응답코드
            200: SUCCESS 셀러정보 겟 완료
            400: INVALID_ACCOUNT_NO, INVALID_AUTH_TYPE_ID
            403: NO_AUTHORIZATION
            500: NO_DATABASE_CONNECTION, DB_CURSOR_ERROR, INVALID_KEY

        Authors:
            [email protected] (이종민)

        History:
            2020-04-01 ([email protected]): 초기 생성
            2020-04-02 ([email protected]): 파라미터 validation 추가, 데코레이터 적용
            2020-04-03 ([email protected]): 주석 수정(메인문구, url parameter 수정)
            2020-04-06 ([email protected]):
                url path 변경('/<int:parameter_account_no>/info' -> '/<int:parameter_account_no>')

        """

        # validation 확인이 된 data 를 account_info 로 재정의
        account_info = {
            'parameter_account_no': args[0],
            'auth_type_id': g.account_info['auth_type_id'],
            'decorator_account_no': g.account_info['account_no'],
        }

        db_connection = get_db_connection()
        try:
            if db_connection:
                seller_service = SellerService()
                getting_seller_info_result = seller_service.get_seller_info(
                    account_info, db_connection)

                return getting_seller_info_result

            else:
                return jsonify({'message': 'NO_DATABASE_CONNECTION'}), 500

        except Exception as e:
            return jsonify({'message': f'{e}'}), 500

        finally:
            try:
                db_connection.close()
            except Exception as e:
                return jsonify({'message': f'{e}'}), 500

    @seller_app.route('/mypage', methods=['GET'], endpoint='get_my_page')
    @login_required
    def get_my_page():
        """ 계정 셀러정보 표출 엔드포인트(my_page)

        mypage 셀러정보를 표출하는 엔드포인트 입니다.
        로그인 데코레이터로 셀러의 계정 번호를 확인합니다.

        확인된 계정 정보를 service 로 넘겨줍니다.

        g.account_info: 데코레이터에서 넘겨받은 계정 정보
            auth_type_id: 계정의 권한정보
            account_no: 데코레이터에서 확인된 계정번호

        Returns: http 응답코드
            200: SUCCESS 셀러정보 겟 완료
            400: INVALID_ACCOUNT_NO, INVALID_AUTH_TYPE_ID
            403: NO_AUTHORIZATION
            500: NO_DATABASE_CONNECTION, DB_CURSOR_ERROR, INVALID_KEY

        Authors:
            [email protected] (이종민)

        History:
            2020-04-08 ([email protected]): 초기 생성

        """

        # get_seller_info dao 를 같이 쓰기 위해 account_no를 아래와 같이 저장
        account_info = {
            'parameter_account_no': g.account_info['account_no'],
            'auth_type_id': g.account_info['auth_type_id']
        }

        try:
            db_connection = get_db_connection()
            if db_connection:
                seller_service = SellerService()
                getting_seller_info_result = seller_service.get_my_page(
                    account_info, db_connection)

                return getting_seller_info_result

            else:
                return jsonify({'message': 'NO_DATABASE_CONNECTION'}), 500

        except Exception as e:
            return jsonify({'message': f'{e}'}), 500

        finally:
            try:
                db_connection.close()

            except Exception as e:
                return jsonify({'message': f'{e}'}), 500

    @seller_app.route('/<int:parameter_account_no>',
                      methods=['PUT'],
                      endpoint='change_seller_info')
    @login_required
    @validate_params(
        Param('parameter_account_no', PATH, int),
        Param('seller_status_no', FORM, int),
        Param('seller_type_no', FORM, int),
        Param('name_kr',
              FORM,
              str,
              rules=[Pattern(r'^[가-힣a-zA-Z0-9\ ]{1,45}$')]),
        Param('name_en', FORM, str, rules=[Pattern(r'^[a-z\ ]{1,45}$')]),
        Param('background_image_url',
              FORM,
              str,
              required=False,
              rules=[MaxLength(200)]),
        Param('brandi_app_user_app_id',
              FORM,
              str,
              rules=[Pattern(r'^[가-힣a-zA-Z0-9]{1,45}$')]),
        Param('ceo_name',
              FORM,
              str,
              rules=[Pattern(r'^[가-힣a-zA-Z0-9]{1,45}$')]),
        Param('company_name',
              FORM,
              str,
              rules=[Pattern(r'^[가-힣a-zA-Z0-9]{1,45}$')]),
        Param('business_number',
              FORM,
              str,
              rules=[Pattern(r'^[0-9]{3}-{1}[0-9]{2}-{1}[0-9]{5}$')]),
        Param('online_business_number', FORM, str, rules=[MaxLength(45)]),
        Param('short_description', FORM, str, rules=[MaxLength(100)]),
        Param('long_description',
              FORM,
              str,
              required=False,
              rules=[MaxLength(200)]),
        Param('long_description',
              FORM,
              str,
              required=False,
              rules=[MinLength(10)]),
        Param('site_url', FORM, str, rules=[MaxLength(200)]),
        Param(
            'site_url',
            FORM,
            str,
            rules=[
                Pattern(
                    r"^(?:http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~:/?#[\]@!\$&'\(\)\*\+,;=.]+$"
                )
            ]),
        Param('manager_infos', FORM, str),
        Param('insta_id', FORM, str, rules=[Pattern(r"^[a-z0-9_\.]{1,45}$")]),
        Param('center_number', FORM, str, rules=[Pattern(r"^[0-9-]{1,14}$")]),
        Param('kakao_id',
              FORM,
              str,
              required=False,
              rules=[Pattern(r"^[가-힣a-zA-Z0-9_\.]{1,45}$")]),
        Param('yellow_id',
              FORM,
              str,
              required=False,
              rules=[Pattern(r"^[가-힣a-zA-Z0-9_\.]{1,45}$")]),
        Param('zip_code', FORM, str, rules=[Pattern(r"^[0-9]{5}$")]),
        Param('address', FORM, str, rules=[MaxLength(100)]),
        Param('detail_address', FORM, str, rules=[MaxLength(100)]),
        Param(
            'weekday_start_time',
            FORM,
            str,
            rules=[
                Pattern(
                    r"^(00|0[0-9]|1[0-9]|2[0-3]):([0-9]|[0-5][0-9]):([0-9]|[0-5][0-9])$"
                )
            ]),
        Param(
            'weekday_end_time',
            FORM,
            str,
            rules=[
                Pattern(
                    r"^(00|0[0-9]|1[0-9]|2[0-3]):([0-9]|[0-5][0-9]):([0-9]|[0-5][0-9])$"
                )
            ]),
        Param(
            'weekend_start_time',
            FORM,
            str,
            required=False,
            rules=[
                Pattern(
                    r"^(00|0[0-9]|1[0-9]|2[0-3]):([0-9]|[0-5][0-9]):([0-9]|[0-5][0-9])$"
                )
            ]),
        Param(
            'weekend_end_time',
            FORM,
            str,
            required=False,
            rules=[
                Pattern(
                    r"^(00|0[0-9]|1[0-9]|2[0-3]):([0-9]|[0-5][0-9]):([0-9]|[0-5][0-9])$"
                )
            ]),
        Param('bank_name', FORM, str, rules=[MaxLength(45)]),
        Param('bank_holder_name', FORM, str, rules=[MaxLength(45)]),
        Param('account_number', FORM, str, rules=[MaxLength(45)]),

        # int 를 str 로 인식해서 정규식 유효성 확인
        Param('seller_status_no', FORM, str, rules=[Pattern(r"^[1-6]{1}$")]),
        Param('seller_type_no', FORM, str, rules=[Pattern(r"^[1-7]{1}$")]),

        # 이미지 url 이 들어올 경우 유효성 확인
        Param(
            'profile_image_url',
            FORM,
            str,
            required=False,
            rules=[
                Pattern(
                    r"^(?:http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~:/?#[\]@!\$&'\(\)\*\+,;=.]+$"
                )
            ]),
        Param(
            'certificate_image_url',
            FORM,
            str,
            required=False,
            rules=[
                Pattern(
                    r"^(?:http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~:/?#[\]@!\$&'\(\)\*\+,;=.]+$"
                )
            ]),
        Param(
            'online_business_image_url',
            FORM,
            str,
            required=False,
            rules=[
                Pattern(
                    r"^(?:http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~:/?#[\]@!\$&'\(\)\*\+,;=.]+$"
                )
            ]),
        Param(
            'background_image_url',
            FORM,
            str,
            required=False,
            rules=[
                Pattern(
                    r"^(?:http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~:/?#[\]@!\$&'\(\)\*\+,;=.]+$"
                )
            ]),
        Param('profile_image_url',
              FORM,
              str,
              required=False,
              rules=[MaxLength(200)]),
        Param('certificate_image_url',
              FORM,
              str,
              required=False,
              rules=[MaxLength(200)]),
        Param('online_business_image_url',
              FORM,
              str,
              required=False,
              rules=[MaxLength(200)]))
    def change_seller_info(*args):
        """ 계정 셀러정보 수정 엔드포인트

        셀러정보를 수정하는 엔드포인트 입니다.
        url 로 셀러정보를 수정하고 싶은 계정 번호를 받습니다.
        셀러정보 수정을 수행하려는 계정의 정보를 데코레이터로부터 받습니다.
        수정하려는 내용은 form 으로 받습니다.

        받은 정보를 유효성 검사를 거친 후 account_info 로 저장해 service 로 넘겨줍니다.

        Args:
            *args: 유효성 검사를 통과한 파라미터

        url parameter:
            parameter_account_no: 저장할 셀러정보의 계정 번호

        g.account_info: 데코레이터에서 넘겨받은 수정을 수행하는 계정 정보
            auth_type_id: 계정의 권한정보
            account_no: 데코레이터에서 확인된 계정번호

        form:
            seller_status_no 셀러 상태번호 int
            seller_type_no 셀러 속성번호 int
            name_kr 셀러 한글명 str 한글,영문,숫자
            name_en 셀러 영문명 str required False 영문 소문자
            brandi_app_user_app_id 브랜디앱 유저 아이디 str
            ceo_name 대표자명 str
            company_name 사업자명 str
            business_number 사업자번호 str 12자리
            online_business_number 통신판매업번호 str
            short_description 셀러 한줄 소개 str
            long_description 셀러 상세 소개 str required False 10글자 이상
            site_url 사이트 URL str
            manager_info: 담당자 정보 list
            [
                {
                    name 담당자명 str
                    contact_number 담당자 핸드폰번호 str
                    email 담당자 이메일 str
                }
            ]
            insta_id 인스타그램 아이디 str
            center_number 고객센터 전화번호 str
            kakao_id 카카오톡 아이디 str required False
            yellow_id 옐로우 아이디 str required False
            zip_code 우편번호 str
            address 주소 int
            detail_address 상세주소 str
            weekday_start_time 고객센터 운영 시작시간(주중) str
            weekday_end_time 고객센터 운영 종료시간(주중) str
            weekend_start_time 고객센터 운영 시작시간(주말) str required False
            weekend_end_time 고객센터 운영 종료시간(주말) str required False
            bank_name 정산은행 str
            bank_holder_name 계좌주 str
            account_number 계좌번호 str
            previous_seller_status_no 이전 셀러정보의 상태번호 int (상태변경이력 체크용)

        Returns: http 응답코드
            200: SUCCESS 셀러정보 수정(새로운 이력 생성) 완료
            400: INVALID_APP_ID (존재하지 않는 브랜디 앱 아이디 입력)
            400: VALIDATION_ERROR_MANAGER_INFO, NO_SPECIFIC_MANAGER_INFO,
                 INVALID_AUTH_TYPE_ID, NO_PROFILE_IMAGE, NO_CERTIFICATE_IMAGE
                 NO_CHANGEABLE_STATUS, EXISTING_NAME_KR, EXISTING_NAME_EN
            403: NO_AUTHORIZATION, NO_AUTHORIZATION_FOR_STATUS_CHANGE, NO_ONLINE_BUSINESS_IMAGE
            500: INVALID_KEY, DB_CURSOR_ERROR, NO_DATABASE_CONNECTION

        Authors:
            [email protected] (이종민)
            [email protected] (윤희철)

        History:
            2020-04-03 ([email protected]): 초기 생성
            2020-04-04 ([email protected]): 이전 셀러 정보번호, 이전 셀러 정보 상태정보, 셀러 계정번호 추가
            2020-04-06 ([email protected]):
                url path 변경('/<int:parameter_account_no>/info' -> '/<int:parameter_account_no>')
            2020-04-08 ([email protected]):
                마스터가 아닐 때 셀러 상태(입점 등)를 변경하려고 하면 에러 처리하는 내용 추가
            2020-04-09 ([email protected]):
                이미지 업로더 적용.
            2020-04-09 ([email protected]):
                 이미지 파일을 새로 업로드하면, 이 파일을 저장한 s3 url 을 저장하고,
                 수정을 안해서 기존에 DB에 저장된 url 을 보내주면, 해당 url 을 저장함
                 필수값인 셀러 프로필, 등록증 2개가 들어오지 않으면 에러처리
            2020-04-12 ([email protected]):
                셀러용 이미지 업로더를 사용하는 것에서 공통 업로더를 사용하도록 변경
        """

        # manager_infos 유효성 확인을 확인하기 위해 따로 저장
        manager_info_list = json.loads(args[16])

        # manger_infos 리스트를 돌면서 각각의 유효성을 충족하는지 체크
        for info in manager_info_list:
            name_validation = r'^[가-힣a-zA-Z0-9\ ]{1,45}$'
            contact_number_validation = r'^[0-9-]{1,14}$'
            email_validation = r'^[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*@[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*.[a-zA-Z]{2,3}$'
            ranking_validation = r'^[1-3]{1}$'

            # 각 키가 들어왔는지 먼저 확인
            if (info.get('name', None) and info.get('contact_number', None)
                    and info.get('email', None) and info.get('ranking', None)):

                # 키가 모두 들어왔으면, 유효성을 만족하는지 확인
                if (re.match(name_validation, info['name']) and re.match(
                        contact_number_validation, info['contact_number'])
                        and re.match(email_validation, info['email']) and
                        re.match(ranking_validation, str(info['ranking']))):
                    pass

                # 유효성을 만족시키지 못하면 에러 반환
                else:
                    return jsonify(
                        {"message": "VALIDATION_ERROR_MANAGER_INFO"}), 400

            # 키가 안들어오면 에러 반환
            else:
                return jsonify({"message": "NO_SPECIFIC_MANAGER_INFO"}), 400

        # 이미지 업로드 함수를 호출해서 이미지를 업로드하고 url 을 딕셔너리로 가져옴.
        image_upload = ImageUpload()
        seller_image = image_upload.upload_images(request)

        if (400 in seller_image) or (500 in seller_image):
            return seller_image

        # validation 확인이 된 data 를 account_info 로 재정의
        account_info = {
            'auth_type_id':
            g.account_info['auth_type_id'],
            'decorator_account_no':
            g.account_info['account_no'],
            'parameter_account_no':
            args[0],
            'profile_image_url':
            seller_image.get('seller_profile_image', None),
            'seller_status_no':
            args[1],
            'seller_type_no':
            args[2],
            'name_kr':
            args[3],
            'name_en':
            args[4],
            'brandi_app_user_app_id':
            args[6],
            'ceo_name':
            args[7],
            'company_name':
            args[8],
            'business_number':
            args[9],
            'certificate_image_url':
            seller_image.get('certificate_image', None),
            'online_business_number':
            args[10],
            'online_business_image_url':
            seller_image.get('online_business_image', None),
            'background_image_url':
            seller_image.get('background_image', None),
            'short_description':
            args[11],
            'long_description':
            args[12],
            'site_url':
            args[14],
            'manager_infos':
            manager_info_list,
            'insta_id':
            args[17],
            'center_number':
            args[18],
            'kakao_id':
            args[19],
            'yellow_id':
            args[20],
            'zip_code':
            args[21],
            'address':
            args[22],
            'detail_address':
            args[23],
            'weekday_start_time':
            args[24],
            'weekday_end_time':
            args[25],
            'weekend_start_time':
            args[26],
            'weekend_end_time':
            args[27],
            'bank_name':
            args[28],
            'bank_holder_name':
            args[29],
            'account_number':
            args[30],
        }

        # file 로 이미지가 안들어올 경우, FORM 으로 받은 이미지 url 로 대체
        if not account_info['profile_image_url']:
            account_info['profile_image_url'] = args[33]

        if not account_info['certificate_image_url']:
            account_info['certificate_image_url'] = args[34]

        if not account_info['online_business_image_url']:
            account_info['online_business_image_url'] = args[35]

        if not account_info['background_image_url']:
            account_info['background_image_url'] = args[36]

        # 이미지 url 필수값 3개가 안들어오면 에러 리턴
        if not account_info['profile_image_url']:
            return jsonify({'message': 'NO_PROFILE_IMAGE'}), 400

        if not account_info['certificate_image_url']:
            return jsonify({'message': 'NO_CERTIFICATE_IMAGE'}), 400

        if not account_info['online_business_image_url']:
            return jsonify({'message': 'NO_ONLINE_BUSINESS_IMAGE'}), 400

        # 데이터베이스 연결
        try:
            db_connection = get_db_connection()
            if db_connection:
                seller_service = SellerService()
                changing_seller_info_result = seller_service.change_seller_info(
                    account_info, db_connection)
                return changing_seller_info_result
            else:
                return jsonify({'message': 'NO_DATABASE_CONNECTION'}), 500

        except Exception as e:
            return jsonify({'message': f'{e}'}), 500

        finally:
            try:
                db_connection.close()
            except Exception as e:
                return jsonify({'message': f'{e}'}), 500

    @seller_app.route('/<int:parameter_account_no>/password',
                      methods=['PUT'],
                      endpoint='change_password')
    @login_required
    @validate_params(
        Param('parameter_account_no', PATH, int),
        Param('original_password', JSON, str, required=False),
        Param('new_password', JSON, str),
    )
    def change_password(*args):
        """ 계정 비밀번호 변경 엔드포인트

        계정 비밀번호 변경 엔드포인트 입니다.
        계정이 가진 권한에 따라 지정된 인자를 받아 비밀번호를 변경합니다.
        url 에 비밀번호를 바꿔야할 계정 번호를 받습니다.

        Args:
            *args: 유효성 검사를 통과한 파라미터

        url parameter:
            parameter_account_no: 비밀번호가 바뀌어야할 계정 번호

        g.account_info: 데코레이터에서 넘겨받은 계정 정보(비밀번호 변경을 수행하려는 계정)
            auth_type_id: 계정의 권한정보
            account_no: 계정번호

        request.body: request 로 전달 받은 정보
            original_password: 기존 비밀번호(비밀번호 변경을 수행하는자가 셀러 권한일 경우에만 전달 받음)
            new_password: 변경하려는 새로운 비밀번호

        Returns: http 응답코드
            200: SUCCESS 비밀번호 변경 완료
            400: VALIDATION_ERROR, INVALID_AUTH_TYPE_ID, NO_ORIGINAL_PASSWORD
                 TOO_SHORT_PASSWORD, INVALID_PARAMETER_ACCOUNT_NO
            401: INVALID_PASSWORD
            403: NO_AUTHORIZATION
            500: DB_CURSOR_ERROR, NO_DATABASE_CONNECTION

        Authors:
            [email protected] (이종민)

        History:
            2020-03-31 ([email protected]): 초기 생성
            2020-04-02 ([email protected]): 파라미터 validation 추가, 데코레이터 적용
            2020-04-06 ([email protected]):
                url path 변경('/<int:parameter_account_no>' -> '/<int:parameter_account_no>/password')
            2020-04-12 ([email protected]): 리팩토링
                - 주석 수정 : 비밀번호 변경을 수행하려는 주체에 대해 명확히 명시
                - 변경할 비밀번호 길이 제한 추가(4글자 이상)
                - 전달 인자 명칭을 명확히 하기 위해 변경(account_info -> change_info)

        """

        # validation 확인이 된 data 를 change_info 로 재정의
        change_info = {
            'parameter_account_no': args[0],
            'original_password': args[1],
            'new_password': args[2],
            'auth_type_id': g.account_info['auth_type_id'],
            'decorator_account_no': g.account_info['account_no']
        }

        # 셀러 권한일 때 original_password 가 없으면 에러반환
        if change_info['auth_type_id'] == 2:
            if change_info['original_password'] is None:
                return jsonify({"message": "NO_ORIGINAL_PASSWORD"}), 400

        # 변경할 비밀번호 길이가 4글자 미만이면 에러 반환
        if len(change_info['new_password']) < 4:
            return jsonify({'message': 'TOO_SHORT_PASSWORD'}), 400

        try:
            db_connection = get_db_connection()
            if db_connection:
                seller_service = SellerService()
                changing_password_result = seller_service.change_password(
                    change_info, db_connection)
                return changing_password_result

            else:
                return jsonify({'message': 'NO_DATABASE_CONNECTION'}), 500

        except Exception as e:
            return jsonify({'message': f'{e}'}), 500

        finally:
            try:
                db_connection.close()
            except Exception as e:
                return jsonify({'message': f'{e}'}), 500

    @seller_app.route('/<int:seller_account_id>/status',
                      methods=['PUT'],
                      endpoint='change_seller_status')
    @login_required
    @validate_params(Param('seller_account_id', PATH, int, required=False),
                     Param('seller_status_id', JSON, int, required=False))
    def change_seller_status(*args):
        """ 셀러 상태 변경
        마스터 권한을 가진 어카운트가 셀러의 상태를 변경 하는 기능.
        path parameter 로 셀러 계정 번호를 받고 body 로 변경하고자 하는 셀러 상태 번호를 받는다.

        Args:
            args: 유효성 검사를 통과한 파라미터 리스트
                seller_account_id: path parameter 를 통해서 들어온 셀러 계정 번호
                seller_status_id: request body 를 통해서 들어온

        Returns:
            status_change_result: seller_service 에서 넘겨받은 결과 값.
            400: seller_service 의 클래스 호출 실패 또는 parameter 를 제대로 넘겨지주 못했을 경우
            500: 데이터베이스 연결 실패

        Authors:
            [email protected] (윤희철)

        History:
            2020-04-05 ([email protected]): 초기 생성
            2020-04-09 ([email protected]): 선분이력을 반영하여 상태변경 작성
        """

        # 유저정보를 가져와 서비스로 넘김
        user = g.account_info

        # 유효성검사를 통과한 parameter 를 딕셔너리 담는다.
        target_seller_info = {
            'seller_account_id': args[0],
            'seller_status_id': args[1]
        }

        try:
            db_connection = DatabaseConnection()
            if db_connection:
                seller_service = SellerService()
                status_change_result = seller_service.change_seller_status(
                    target_seller_info, user, db_connection)
                return status_change_result
            else:
                return jsonify({'message': 'NO_DATABASE_CONNECTION'}), 500

        except Exception as e:
            return ({'message': f'{e}'}), 400
class BoardView:
    board_app = Blueprint('board_app', __name__, url_prefix='/board')

    @board_app.route("", methods=["POST"], endpoint='make_board')
    @login_required
    @validate_params(Param('name', JSON, str, rules=[MaxLength(20)]))
    def make_board(*args):
        user_info = g.user_info
        if user_info.get('auth_type_id') != 1:
            return jsonify({'message': 'UNAUTHORIZED_ACTION'}), 400

        board_info = {'uploader': user_info['user_id'], 'name': args[0]}
        board_service = BoardService()
        result = board_service.make_board(board_info)
        return result

    @board_app.route("", methods=["GET"], endpoint='get_board_list')
    @login_required
    @validate_params(Param('offset', GET, int, required=False),
                     Param('limit', GET, int, required=False),
                     Param('name', GET, str, required=False))
    def get_board_list(*args):
        board_info = {
            'offset': args[0] if args[0] else 0,
            'limit': args[1] if args[1] else 10,
            'name': args[2]
        }
        board_service = BoardService()
        boards = board_service.get_board_list(board_info)
        return jsonify({'board_list': boards}), 200

    @board_app.route("/<int:board_id>", methods=["PUT"], endpoint='edit_board')
    @login_required
    @validate_params(Param('board_id', PATH, int),
                     Param('name', JSON, str, rules=[MaxLength(20)]))
    def edit_board(*args):
        user_info = g.user_info
        if user_info.get('auth_type_id') != 1:
            return jsonify({'message': 'UNAUTHORIZED_ACTION'}), 400

        board_info = {
            'modifier': user_info['user_id'],
            'board_id': args[0],
            'new_name': args[1]
        }
        board_service = BoardService()
        result = board_service.edit_board(board_info)
        return result

    @board_app.route("/<int:board_id>",
                     methods=["DELETE"],
                     endpoint='delete_board')
    @login_required
    @validate_params(Param('board_id', PATH, int),
                     Param('is_deleted', JSON, int))
    def delete_board(*args):
        user_info = g.user_info
        if user_info.get('auth_type_id') != 1:
            return jsonify({'message': 'UNAUTHORIZED_ACTION'}), 400

        board_info = {
            'modifier': user_info['user_id'],
            'board_id': args[0],
        }

        if args[1] == 1:
            board_info['is_deleted'] = True
        else:
            return jsonify({'message': 'INVALID_ACTION'}), 400

        board_service = BoardService()
        result = board_service.delete_board(board_info)
        return result

    @board_app.route("/<int:board_id>",
                     methods=["POST"],
                     endpoint='make_article')
    @login_required
    @validate_params(Param('board_id', PATH, int),
                     Param('title', JSON, str, rules=[MaxLength(200)]),
                     Param('content', JSON, str, rules=[MaxLength(10000)]))
    def make_article(*args):
        user_info = g.user_info
        article_info = {
            'board_id': args[0],
            'uploader': user_info['user_id'],
            'title': args[1],
            'content': args[2]
        }
        board_service = BoardService()
        result = board_service.make_article(article_info)
        return result

    @board_app.route("/<int:board_id>",
                     methods=["GET"],
                     endpoint='get_article_list')
    @login_required
    @validate_params(Param('board_id', PATH, int),
                     Param('offset', GET, int, required=False),
                     Param('limit', GET, int, required=False),
                     Param('title', GET, str, required=False),
                     Param('uploader', GET, str, required=False))
    def get_article_list(*args):
        article_info = {
            'board_id': args[0],
            'offset': args[1] if args[1] else 0,
            'limit': args[2] if args[2] else None,
            'title': args[3],
            'uploader': args[4]
        }
        board_service = BoardService()
        articles = board_service.get_article_list(article_info)
        return jsonify({'article_list': articles}), 200

    @board_app.route("/<int:board_id>/<int:article_id>",
                     methods=["GET"],
                     endpoint='get_article_detail')
    @login_required
    @validate_params(Param('board_id', PATH, int),
                     Param('article_id', PATH, int))
    def get_article_detail(*args):
        article_info = {'board_id': args[0], 'article_id': args[1]}
        board_service = BoardService()
        result = board_service.get_article_detail(article_info)
        return result

    @board_app.route("/<int:board_id>/<int:article_id>",
                     methods=["PUT"],
                     endpoint='edit_article')
    @login_required
    @validate_params(Param('board_id', PATH, int),
                     Param('article_id', PATH, int),
                     Param('title', JSON, str, rules=[MaxLength(200)]),
                     Param('content', JSON, str, rules=[MaxLength(10000)]))
    def edit_article(*args):
        user_info = g.user_info
        article_info = {
            'board_id': args[0],
            'article_id': args[1],
            'new_title': args[2],
            'new_content': args[3],
            'modifier': user_info['user_id'],
            'auth_type_id': user_info['auth_type_id']
        }
        board_service = BoardService()
        result = board_service.edit_article(article_info)
        return result

    @board_app.route("/<int:board_id>/<int:article_id>",
                     methods=["DELETE"],
                     endpoint='delete_article')
    @login_required
    @validate_params(Param('board_id', PATH, int),
                     Param('article_id', PATH, int),
                     Param('is_deleted', JSON, int))
    def delete_article(*args):
        user_info = g.user_info
        article_info = {
            'board_id': args[0],
            'article_id': args[1],
            'modifier': user_info['user_id'],
            'auth_type_id': user_info['auth_type_id']
        }

        if args[2] == 1:
            article_info['is_deleted'] = True
        else:
            return jsonify({'message': 'INVALID_ACTION'}), 400

        board_service = BoardService()
        result = board_service.delete_article(article_info)
        return result

    @board_app.route("/recent-articles",
                     methods=["GET"],
                     endpoint='get_recent_board_article')
    @login_required
    @validate_params(
        Param('offset', GET, int, required=False),
        Param('limit', GET, int, required=False),
    )
    def get_recent_board_article(*args):
        board_info = {'offset': 0, 'limit': None}
        board_service = BoardService()
        board_list = board_service.get_board_list(board_info)

        for board in board_list:
            article_info = {
                'board_id': board['id'],
                'offset': args[0] if args[0] else 0,
                'limit': args[1] if args[1] else 2
            }
            article_list = board_service.get_article_list(article_info)
            board['recent_article_list'] = article_list

        return jsonify({'dashboard': board_list})
class OrderView:
    order_app = Blueprint('order_app', __name__, url_prefix='/orders')

    @order_app.route('/filter_options/<order_status_id>', methods=['GET'])
    @validate_params(
        Param('order_status_id', PATH, int, rules=[Enum(1, 2, 3, 4, 5)]))
    @login_validator
    def get_filter_options(order_status_id):
        # 주문관리 페이지에서 셀러속성 리스트와 주문상태 변경 버튼 보내주는 엔드포인트
        order_service = OrderService()

        connection = None
        try:
            connection = connect_db()
            filter_options = order_service.get_filter_options(
                connection, order_status_id)
            return jsonify(filter_options), 200

        except Exception as e:
            return jsonify({"message": f'{e}'}), 400

        finally:
            try:
                if connection:
                    connection.close()
            except Exception as e:
                return jsonify({"message": f'{e}'}), 500

    @order_app.route('', methods=['GET'])
    @validate_params(
        Param('order_status_id',
              GET,
              int,
              rules=[Enum(1, 2, 3, 4, 5)],
              required=True),
        Param('order_number',
              GET,
              str,
              rules=[Pattern('^[0-9]{16,}$')],
              required=False),
        Param('detailed_order_number',
              GET,
              str,
              rules=[Pattern('^[0-9]{17,}$')],
              required=False),
        Param('buyer_name', GET, str, rules=[Pattern('[\S]')], required=False),
        Param('phone_number',
              GET,
              str,
              rules=[Pattern('^[0-9]{11}$')],
              required=False),
        Param('seller_name', GET, str, rules=[Pattern('[\S]')],
              required=False),
        Param('product_name',
              GET,
              str,
              rules=[Pattern('[\S]')],
              required=False),
        Param(
            'start_date',
            GET,
            str,
            rules=[
                Pattern(
                    '^(20)[\d]{2}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[0-1])$')
            ],
            required=False),
        Param(
            'end_date',
            GET,
            str,
            rules=[
                Pattern(
                    '^(20)[\d]{2}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[0-1])$')
            ],
            required=False),
        # 셀러속성 다중선택 가능
        Param('seller_type_id',
              GET,
              list,
              rules=[MinLength(1), MaxLength(7)],
              required=False),
        Param('limit',
              GET,
              int,
              rules=[Enum(10, 20, 50, 100, 150)],
              required=False),
        Param('order_by',
              GET,
              str,
              rules=[Enum('desc', 'asc')],
              required=False),
        Param('page', GET, int, required=False))
    @login_validator
    def get_order_list(*args):
        # 주문상태별 주문 리스트 필터하여 보내주는 엔드포인트
        order_service = OrderService()

        # page parameter에 0이나 음수가 들어올 경우 키에러
        if args[12] and args[12] <= 0:
            return jsonify({"key error": "Page cannot be negative"}), 400

        order_filter = {
            'order_status_id': args[0],
            'order_number': args[1],
            'detailed_order_number': args[2],
            'buyer_name': args[3],
            'phone_number': args[4],
            'seller_name': args[5],
            'product_name': args[6],
            'start_date': args[7],
            'end_date': args[8],
            'seller_type_id': args[9],
            # 디폴트값: 최신주문일순, 50개씩 보기
            'limit': args[10] if args[10] else 50,
            'order_by': args[11] if args[11] else 'desc',
            'page': args[12] if args[12] else 1
        }

        # 셀러일 경우 필터에 seller_id 추가
        if g.token_info['account_type_id'] == 2:
            order_filter['seller_id'] = g.token_info['seller_id']

        connection = None
        try:
            connection = connect_db()
            order_list = order_service.get_order_list(connection, order_filter)
            return jsonify(order_list), 200

        except Exception as e:
            return jsonify({"message": f"{e}"}), 400

        finally:
            try:
                if connection:
                    connection.close()
            except Exception as e:
                return jsonify({"message": f"{e}"}), 500

    @order_app.route('', methods=['POST'])
    @validate_params(
        Param('order_item_id', JSON, list, required=True),
        # 2: 상품준비 3: 배송중
        Param('order_status_id', GET, int, rules=[Enum(2, 3)], required=True),
        # 1: 배송처리 2: 배송완료처리
        Param('order_action_id', JSON, int, rules=[Enum(1, 2)], required=True))
    @login_validator
    def update_order_status(*args):
        # 주문 id를 리스트로 받아서 일괄적으로 주문 상태를 업데이트하는 엔드포인트
        order_service = OrderService()

        update_status = {
            'editor_id': g.token_info['account_id'],
            'order_item_id': args[0],
            'order_status_id': args[1],
            'order_action_id': args[2]
        }

        connection = None
        try:
            connection = connect_db()
            number_of_orders_updated = order_service.update_order_status(
                connection, update_status)
            connection.commit()
            return jsonify({
                "message":
                f"{number_of_orders_updated} order(s) successfully updated"
            }), 201

        except Exception as e:
            connection.rollback()
            return jsonify({"message": f"{e}"}), 400

        finally:
            try:
                if connection:
                    connection.close()
            except Exception as e:
                return jsonify({"message": f"{e}"}), 500

    @order_app.route('/<order_item_id>', methods=['GET'])
    @validate_params(Param('order_item_id', PATH, int))
    @login_validator
    def get_order_detail(order_item_id):
        # 주문 상세정보 가져오는 엔드포인트

        order_service = OrderService()

        order_filter = {'order_item_id': order_item_id}

        # # 셀러일 경우 필터에 seller_id 추가
        # if g.token_info['account_type_id'] == 2:
        #     order_filter['seller_id'] = g.token_info['seller_id']

        connection = None
        try:
            connection = connect_db()
            order_detail = order_service.get_order_detail(
                connection, order_filter)
            return jsonify(order_detail), 200

        except Exception as e:
            return jsonify({"message": f"{e}"}), 400

        finally:
            try:
                if connection:
                    connection.close()
            except Exception as e:
                return jsonify({"message": f"{e}"}), 500

    @order_app.route('/<order_item_id>', methods=['POST'])
    @validate_params(Param('order_item_id', PATH, int, required=True),
                     Param('new_order_status_id',
                           JSON,
                           int,
                           rules=[Enum(2, 3, 4, 5)],
                           required=False),
                     Param('phone_number',
                           JSON,
                           str,
                           rules=[Pattern('^[0-9]{11}')],
                           required=False),
                     Param('address_1',
                           JSON,
                           str,
                           rules=[Pattern('[\S]')],
                           required=False),
                     Param('address_2',
                           JSON,
                           str,
                           rules=[Pattern('[\S]')],
                           required=False),
                     Param('zip_code',
                           JSON,
                           str,
                           rules=[Pattern('^[0-9]{5}')],
                           required=False),
                     Param('delivery_instruction', JSON, str, required=False))
    @login_validator
    def update_order_detail(*args):
        # 주문 상세정보(주문상태, 연락처, 배송지 정보)를 업데이트하고 업데이트된 정보를 리턴

        order_service = OrderService()

        order_filter = {'order_item_id': args[0]}

        update_order = {
            'editor_id': g.token_info['account_id'],
            'order_item_id': args[0],
            'new_order_status_id': args[1],
            'phone_number': args[2],
            'address_1': args[3],
            'address_2': args[4],
            'zip_code': args[5],
            'delivery_instruction': args[6],
        }

        # 셀러일 경우 필터에 seller_id 추가
        if g.token_info['account_type_id'] == 2:
            order_filter['seller_id'] = g.token_info['seller_id']

        connection = None
        try:
            connection = connect_db()
            order_service.update_order_detail(connection, update_order)
            updated_order_detail = order_service.get_order_detail(
                connection, order_filter)
            connection.commit()
            return jsonify({"updated_order_detail": updated_order_detail}), 201

        except Exception as e:
            connection.rollback()
            return jsonify({"message": f"{e}"}), 400

        finally:
            try:
                if connection:
                    connection.close()
            except Exception as e:
                return jsonify({"message": f"{e}"})
class EventView:
    """ 기획전 뷰

    Authors:
        [email protected] (이종민)
    History:
        2020-04-07 ([email protected]): 초기생성

    """

    event_app = Blueprint('event_app', __name__, url_prefix='/event')

    @event_app.route("", methods=["GET"], endpoint='get_all_events')
    @login_required
    @validate_params(Param('event_type_id', GET, list, required=False),
                     Param('event_name', GET, str, required=False),
                     Param('event_start_time', GET, str, required=False),
                     Param('event_end_time', GET, str, required=False),
                     Param('offset', GET, int, required=False),
                     Param('limit', GET, int, required=False))
    def get_all_events(*args):
        """ 등록된 모든 이벤트 목록 표출 엔드포인트

        Args:
            *args: 이벤트 정보
                event_type_id: 이벤트 타입
                event_name: 검색어에 포함되는 이벤트 이름
                event_start_time: 검색할 이벤트 등록 날짜 시작 지점
                event_end_time: 검색할 이벤트 등록 날짜 끝 지점

        Returns:
            200: 검색 조건에 맞는 이벤트 목록
            400: 유효하지 않은 검색 날짜 조건
            500: 데이터베이스 에러

        Authors:
            [email protected] (이소헌)

        History:
            2020-04-12 ([email protected]): 초기 생성
        """
        event_info = {
            'auth_type_id': g.account_info['auth_type_id'],
            'event_type_id': args[0],
            'event_name': args[1],
            'event_start_time': args[2],
            'event_end_time': args[3],
            'offset': args[4] if args[4] else 0,
            'limit': args[5] if args[5] else 10
        }
        if event_info['event_start_time'] and event_info['event_end_time']:
            if (datetime.strptime(event_info['event_start_time'], "%Y-%m-%d") \
                    > datetime.strptime(event_info['event_end_time'], "%Y-%m-%d")):
                return jsonify({'message': 'INVALID_EVENT_DATE'}), 400

        db_connection = None
        try:
            db_connection = get_db_connection()
            if db_connection:
                event_service = EventService()
                events = event_service.get_all_events(event_info,
                                                      db_connection)
                return events
            else:
                return jsonify({'message': 'NO_DATABASE_CONNECTION'}), 500

        except Exception as e:
            return jsonify({'message': f'{e}'}), 500

        finally:
            try:
                if db_connection:
                    db_connection.close()
            except Exception as e:
                return jsonify({'message': f'{e}'}), 500

    @event_app.route("/<int:event_no>",
                     methods=["GET"],
                     endpoint='get_event_infos')
    @login_required
    def get_event_infos(event_no):
        """ 기획전 정보 표출 엔드포인트

        기획전 정보 표출 엔드포인트 입니다.
        url parameter 로 받은 기획전 번호에 해당하는 정보를 표출합니다.

        Args:
            event_no: 기획전 번호

        Returns:
            200: 기획전 정보
            400: INVALID_EVENT_NO
            500: DB_CURSOR_ERROR, INVALID_KEY, NO_DATABASE_CONNECTION

        Authors:
            [email protected] (이종민)
            [email protected] (윤희철)

        History:
            2020-04-10 ([email protected]): 초기 생성
            2020-04-14 ([email protected]): 데이터베이스 커넥션 호출 시 try-catch 추가

        """

        # 마스터 권한이 아니면 반려
        if g.account_info['auth_type_id'] != 1:
            return jsonify({'message': 'NO_AUTHORIZATION'}), 403

        try:
            db_connection = get_db_connection()
            if db_connection:
                event_service = EventService()
                info = event_service.get_event_infos(event_no, db_connection)
                return info
            else:
                return jsonify({'message': 'NO_DATABASE_CONNECTION'}), 500

        except Exception as e:
            return jsonify({'message': f'{e}'}), 500

        finally:
            try:
                db_connection.close()
            except Exception as e:
                return jsonify({'message': f'{e}'}), 500

    @event_app.route('', methods=['POST'], endpoint='register_event_info')
    @login_required
    @validate_params(
        # 전체 기획전 필수값
        Param('event_type_id', FORM, int),
        Param('event_sort_id', FORM, int),
        Param('is_on_main', FORM, str, rules=[Pattern(r"^[0-1]{1}$")]),
        Param('is_on_event', FORM, str, rules=[Pattern(r"^[0-1]{1}$")]),
        Param('name', FORM, str, rules=[Pattern(r"^.{1,25}$")]),
        Param(
            'event_start_time',
            FORM,
            str,
            rules=[
                Pattern(
                    r"^([2][0]\d{2})-([0-2]\d)-([0-2]\d) ([0-2]\d):([0-5]\d)$")
            ]),
        Param(
            'event_end_time',
            FORM,
            str,
            rules=[
                Pattern(
                    r"^([2][0]\d{2})-([0-2]\d)-([0-2]\d) ([0-2]\d):([0-5]\d)$")
            ]),

        # 각 기획전 타입 필수값 여부는 function 내부에서 확인
        Param('short_description',
              FORM,
              str,
              required=False,
              rules=[MaxLength(45)]),
        Param('long_description', FORM, str, required=False),
        Param(
            'banner_image_url',
            FORM,
            str,
            required=False,
            rules=[
                Pattern(
                    r"^(?:http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~:/?#[\]@!\$&'\(\)\*\+,;=.]+$"
                )
            ]),
        Param('banner_image_url',
              FORM,
              str,
              required=False,
              rules=[MaxLength(200)]),
        Param(
            'detail_image_url',
            FORM,
            str,
            required=False,
            rules=[
                Pattern(
                    r"^(?:http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~:/?#[\]@!\$&'\(\)\*\+,;=.]+$"
                )
            ]),
        Param('detail_image_url',
              FORM,
              str,
              required=False,
              rules=[MaxLength(200)]),
        Param('button_name', FORM, str, required=False, rules=[MaxLength(10)]),
        Param('button_link_type_id', FORM, int, required=False),
        Param('button_link_description',
              FORM,
              str,
              required=False,
              rules=[MaxLength(45)]),
        Param('product', FORM, str, required=False),
        Param(
            'youtube_url',
            FORM,
            str,
            required=False,
            rules=[
                Pattern(
                    r"^(?:http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~:/?#[\]@!\$&'\(\)\*\+,;=.]+$"
                )
            ]),
        Param('youtube_url', FORM, str, required=False,
              rules=[MaxLength(200)]),
        Param('event_type_id', FORM, str, rules=[Pattern(r"^[1-5]{1}$")]),
        Param('button_link_type_id',
              FORM,
              str,
              required=False,
              rules=[Pattern(r"^[1-6]{1}$")]))
    def register_event_info(*args):
        """ 기획전 등록 엔드포인트

        기획전을 신규 등록하는 엔드포인트 입니다.
        request.body 로 등록 정보를 받고, 유효성을 확인합니다.
        기획전 전 타입 공통 필수 파라미터는 먼저 확인하고,
        각 타입별 필수 파라미터는 function 내에서 확인합니다.

        확인이 끝나면 event_info 에 모든 파라미터를 저장합니다.
        등록을 수행하는 계정의 정보를 데코레이터에서 받아와 event_info 에 저장합니다.

        function 진입 후 마스터 권한이 없으면 에러를 리턴하고,
        마스터 권한이면 서비스로 값을 넘깁니다.

        Args:
            *args: 유효성 검사를 통과한 파라미터

        request.body:
            event_type_id 기획전 타입 외래키
            event_sort_id 기획전 종류 외래키
            is_on_main 메인 노출여부
            is_on_event 기획전 진열여부
            name 기획전명
            event_start_time 기획전 시작시간 (ex) 2020-04-10 23:59
            event_end_time 기획전 종료시간
            short_description 기획전 간략설명
            long_description 기획전 상세설명
            banner_image_url 배너 이미지 url
            detail_image_url 상세 이미지 url
            button_name 이벤트 버튼 이름
            button_link_type_id 이벤트 버튼 링크타입 외래키
            button_link_description 이벤트 버튼링크 내용
            product_order 상품 진열 순서
            product_id 상품 외래키
            youtube_url 유튜브 url

        Returns: http 응답코드
            200: SUCCESS 기획전 신규 등록 완료
            400: NO_SHORT_DESCRIPTION, BANNER_IMAGE_URL, NO_DETAIL_IMAGE_URL,
                 NO_BUTTON_NAME, NO_BUTTON_LINK_DESCRIPTION, INVALID_EVENT_SORT
            403: NO_AUTHORIZATION
            500: NO_DATABASE_CONNECTION

        Authors:
            [email protected] (이종민)
            [email protected] (윤희철)

        History:
            2020-04-07 ([email protected]): 초기생성 / 이벤트 기획전 부분 작성
            2020-04-08 ([email protected]): 기획전 기간 밸리데이션 추가
            2020-04-10 ([email protected]):
                - 상품(이미지), 상품(텍스트), 유튜브 기획전 작성
                - request form 형태로 오는 매니저 정보 list 를 parsing 해서 사용하는 로직 추가
                - 데이터베이스 커넥션을 호출 할 때 try/except 방식으로 변경
            2020-04-12 ([email protected]):
                - event_type_id 를 int 로 받아오도록 validator 변경
                - 기획전용 이미지 업로더를 사용하는 것에서 공통 업로더를 사용하도록 변경
                - 기획전 상품 정보를 json loads로 파싱하는 과정을 try/except 방식에서 if 문 방식으로 변경
        """
        if g.account_info['auth_type_id'] != 1:
            return jsonify({'message': 'NO_AUTHORIZATION'}), 403

        # 이미지 업로드 함수를 호출해서 이미지를 업로드하고 url 을 딕셔너리로 가져옴.
        image_upload = ImageUpload()
        event_image = image_upload.upload_images(request)

        # 함수의 실행결과에 400이 포함된 경우 애러메세지를 그대로 리턴함.
        if (400 in event_image) or (500 in event_image):
            return event_image

        # validation(형식) 확인된 데이터 저장
        event_info = {
            'event_type_id': args[0],
            'event_sort_id': args[1],
            'is_on_main': args[2],
            'is_on_event': args[3],
            'name': args[4],
            'event_start_time': args[5],
            'event_end_time': args[6],
            'short_description': args[7],
            'long_description': args[8],
            'banner_image_url': event_image.get('banner_image', None),
            'detail_image_url': event_image.get('detail_image', None),
            'button_name': args[13],
            'button_link_type_id': args[14],
            'button_link_description': args[15],
            'youtube_url': args[17],
            'auth_type_id': g.account_info['auth_type_id'],
            'account_no': g.account_info['account_no']
        }

        # file 로 이미지가 안들어올 경우, FORM 으로 받은 이미지 url 로 대체
        if not event_info['banner_image_url']:
            event_info['banner_image_url'] = args[10]

        if not event_info['detail_image_url']:
            event_info['detail_image_url'] = args[12]

        # 딕셔너리를 품은 리스트인 product 정보를 따로 저장 (dao 에서 에러를 막기 위해)
        event_product_info = args[16]

        # form 데이터로 값을 받으면 str 처리가 되기 때문에 json.loads 로 읽을 수 있게 파싱
        if event_product_info:
            event_product_info = json.loads(event_product_info)

        # 기획전 기간 밸리데이션
        now = datetime.strftime(datetime.now(), '%Y-%m-%d %H:%M')

        # 시작시간이 현재 시간보다 전이거나 시작시간이 종료시간보다 늦으면 에러 반환
        if event_info['event_start_time'] < now or event_info[
                'event_start_time'] > event_info['event_end_time']:
            return jsonify({'message': 'INVALID_EVENT_TIME'}), 400

        # 기획전 타입이 이벤트일 경우 필수값과 기획전 종류 범위 확인
        if event_info['event_type_id'] == 1:
            if not event_info['short_description']:
                return jsonify({'message': 'NO_SHORT_DESCRIPTION'}), 400

            if not event_info['banner_image_url']:
                return jsonify({'message': 'NO_BANNER_IMAGE'}), 400

            if not event_info['detail_image_url']:
                return jsonify({'message': 'NO_DETAIL_IMAGE'}), 400

            # 기획전 종류 범위 확인
            if event_info['event_sort_id'] not in range(1, 3):
                return jsonify({'message': 'INVALID_EVENT_SORT'}), 400

        # 기획전 타입이 쿠폰일 경우 필수값 확인
        if event_info['event_type_id'] == 2:
            if not event_info['short_description']:
                return jsonify({'message': 'NO_SHORT_DESCRIPTION'}), 400

            # 기획전 종류 범위 확인
            if event_info['event_sort_id'] not in range(3, 9):
                return jsonify({'message': 'INVALID_EVENT_SORT'}), 400

        # 기획전 타입이 상품(이미지)일 경우 필수값과 기획전 종류 범위 확인
        if event_info['event_type_id'] == 3:
            if not event_info['banner_image_url']:
                return jsonify({'message': 'NO_BANNER_IMAGE'}), 400

            if not event_info['detail_image_url']:
                return jsonify({'message': 'NO_DETAIL_IMAGE'}), 400

            # 기획전 종류 범위 확인
            if event_info['event_sort_id'] not in range(9, 11):
                return jsonify({'message': 'INVALID_EVENT_SORT'}), 400

        # 기획전 타입이 상품(텍스트)일 경우 필수값과 기획전 종류 범위 확인
        if event_info['event_type_id'] == 4:
            if not event_info['short_description']:
                return jsonify({'message': 'NO_SHORT_DESCRIPTION'}), 400

            if not event_info['banner_image_url']:
                return jsonify({'message': 'NO_BANNER_IMAGE'}), 400

            # 기획전 종류 범위 확인
            if event_info['event_sort_id'] not in range(11, 13):
                return jsonify({'message': 'INVALID_EVENT_SORT'}), 400

        # 기획전 타입이 유튜브일 경우 필수값 확인과 기획전 종류 범위 확인
        if event_info['event_type_id'] == 5:
            if not event_info['short_description']:
                return jsonify({'message': 'NO_SHORT_DESCRIPTION'}), 400

            if not event_info['banner_image_url']:
                return jsonify({'message': 'NO_BANNER_IMAGE'}), 400

            if not event_info['youtube_url']:
                return jsonify({'message': 'NO_YOUTUBE_URL'}), 400

            # 기획전 종류 범위 확인
            if event_info['event_sort_id'] not in range(13, 15):
                return jsonify({'message': 'INVALID_EVENT_SORT'}), 400

        # 입력 인자 관계에 따른 필수값 확인
        if event_info['button_link_type_id']:

            # button_link_type_id 가 있을 때 button_name 은 무조건 있어야 함
            if not event_info['button_name']:
                return jsonify({'message': 'NO_BUTTON_NAME'}), 400

            if event_info['button_link_type_id'] in range(4, 7):

                # button_link_type_id가 4~6이면 description 이 있어야 함
                if not event_info['button_link_description']:
                    return jsonify({'message':
                                    'NO_BUTTON_LINK_DESCRIPTION'}), 400

        try:
            # 데이터베이스 연결
            db_connection = get_db_connection()
            if db_connection:
                event_service = EventService()
                registering_event_result = event_service.register_event(
                    event_info, db_connection, event_product_info)
                return registering_event_result

            else:
                return jsonify({'view_message': 'NO_DATABASE_CONNECTION'}), 500

        except Exception as e:
            return jsonify({'message': f'{e}'}), 500

        finally:
            try:
                db_connection.close()

            except Exception as e:
                return jsonify({'message': f'{e}'}), 500

    @event_app.route("/type", methods=["GET"])
    @login_required
    def get_event_types():
        """ 기획전 타입 표출 엔드포인트

        기획전 타입 표출 엔드포인트 입니다.
        기획전 등록페이지에서 기획전 타입 목록을 표출할때 사용됩니다.

        Returns:
            200: 기획전 타입 목록
            500: DB_CURSOR_ERROR, INVALID_KEY, NO_DATABASE_CONNECTION

        Authors:
            [email protected] (이종민)

        History:
            2020-04-09 ([email protected]): 초기 생성

        """
        try:
            db_connection = get_db_connection()
            if db_connection:
                event_service = EventService()
                types = event_service.get_event_types(db_connection)
                return types

            else:
                return jsonify({'message': 'NO_DATABASE_CONNECTION'}), 500

        except Exception as e:
            return jsonify({'message': f'{e}'}), 500

        finally:
            try:
                db_connection.close()

            except Exception as e:
                return jsonify({'message': f'{e}'}), 500

    @event_app.route("/type/<int:event_type_id>",
                     methods=["GET"],
                     endpoint='get_event_sorts')
    @login_required
    @validate_params(
        Param('event_type_id', PATH, str, rules=[Pattern(r"^[1-5]{1}$")]))
    def get_event_sorts(*args):
        """ 기획전 타입별 종류 표출 엔드포인트

        기획전 타입별 종류 표출 엔드포인트 입니다.
        기획전 등록페이지에서 기획전 타입 별 목록을 표출할때 사됩니다.

        Args:
            *args: 유효성 검사를 통과한 파라미터

        url Parameter:
            event_type_id: 기획전 타입 아이디

        Returns:
            200: 기획전 타입별 종류 목록
            500: DB_CURSOR_ERROR, INVALID_KEY, NO_DATABASE_CONNECTION

        Authors:
            [email protected] (이종민)

        History:
            2020-04-09 ([email protected]): 초기 생성

        """

        # event_type_id 저장
        event_type_info = {"event_type_id": args[0]}

        try:
            db_connection = get_db_connection()
            if db_connection:
                event_service = EventService()
                sorts = event_service.get_event_sorts(event_type_info,
                                                      db_connection)
                return sorts

            else:
                return jsonify({'message': 'NO_DATABASE_CONNECTION'}), 500

        except Exception as e:
            return jsonify({'message': f'{e}'}), 500

        finally:
            try:
                db_connection.close()

            except Exception as e:
                return jsonify({'message': f'{e}'}), 500

    @event_app.route("/<int:event_no>",
                     methods=["PUT"],
                     endpoint='change_event_infos')
    @login_required
    @validate_params(
        # 전체 기획전 필수값
        Param('event_no', PATH, int),
        Param('event_type_id', FORM, int),
        Param('event_sort_id', FORM, int),
        Param('is_on_main', FORM, str, rules=[Pattern(r"^[0-1]{1}$")]),
        Param('is_on_event', FORM, str, rules=[Pattern(r"^[0-1]{1}$")]),
        Param('name', FORM, str, rules=[Pattern(r"^.{1,25}$")]),
        Param(
            'event_start_time',
            FORM,
            str,
            rules=[
                Pattern(
                    r"^([2][0]\d{2})-([0-2]\d)-([0-2]\d) ([0-2]\d):([0-5]\d)$")
            ]),
        Param(
            'event_end_time',
            FORM,
            str,
            rules=[
                Pattern(
                    r"^([2][0]\d{2})-([0-2]\d)-([0-2]\d) ([0-2]\d):([0-5]\d)$")
            ]),

        # 각 기획전 타입 필수값 여부는 function 내부에서 확인
        Param('short_description',
              FORM,
              str,
              required=False,
              rules=[MaxLength(45)]),
        Param('long_description', FORM, str, required=False),
        Param(
            'banner_image_url',
            FORM,
            str,
            required=False,
            rules=[
                Pattern(
                    r"^(?:http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~:/?#[\]@!\$&'\(\)\*\+,;=.]+$"
                )
            ]),
        Param('banner_image_url',
              FORM,
              str,
              required=False,
              rules=[MaxLength(200)]),
        Param(
            'detail_image_url',
            FORM,
            str,
            required=False,
            rules=[
                Pattern(
                    r"^(?:http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~:/?#[\]@!\$&'\(\)\*\+,;=.]+$"
                )
            ]),
        Param('detail_image_url',
              FORM,
              str,
              required=False,
              rules=[MaxLength(200)]),
        Param('button_name', FORM, str, required=False, rules=[MaxLength(10)]),
        Param('button_link_type_id', FORM, int, required=False),
        Param('button_link_description',
              FORM,
              str,
              required=False,
              rules=[MaxLength(45)]),
        Param('product', FORM, str, required=False),
        Param(
            'youtube_url',
            FORM,
            str,
            required=False,
            rules=[
                Pattern(
                    r"^(?:http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~:/?#[\]@!\$&'\(\)\*\+,;=.]+$"
                )
            ]),
        Param('youtube_url', FORM, str, required=False,
              rules=[MaxLength(200)]),
        Param('event_type_id', FORM, str, rules=[Pattern(r"^[1-5]{1}$")]),
        Param('button_link_type_id',
              FORM,
              str,
              required=False,
              rules=[Pattern(r"^[1-6]{1}$")]))
    def change_event_infos(*args):
        """ 기획전 정보 수정 엔드포인트

        기획전 정보 수정 엔드포인트 입니다.
        form 과 url parameter 로 등록 정보를 받고, 유효성을 확인합니다.
        기획전 전 타입 공통 필수 파라미터는 먼저 확인하고,
        각 타입별 필수 파라미터는 function 내에서 확인합니다.

        확인이 끝나면 event_info 에 모든 파라미터를 저장합니다.
        등록을 수행하는 계정의 정보를 데코레이터에서 받아와 event_info 에 저장합니다.

        function 진입 후 마스터 권한이 없으면 에러를 리턴하고,
        마스터 권한이면 서비스로 값을 넘깁니다.

        Args:
            *args: 유효성 검사를 통과한 파라미터

        request.form:
            event_no: 기획전 번호
            event_type_id 기획전 타입 외래키
            event_sort_id 기획전 종류 외래키
            is_on_main 메인 노출여부
            is_on_event 기획전 진열여부
            name 기획전명
            event_start_time 기획전 시작시간 (ex) 2020-04-10 23:59
            event_end_time 기획전 종료시간
            short_description 기획전 간략설명
            long_description 기획전 상세설명
            banner_image_url 배너 이미지 url
            detail_image_url 상세 이미지 url
            button_name 이벤트 버튼 이름
            button_link_type_id 이벤트 버튼 링크타입 외래키
            button_link_description 이벤트 버튼링크 내용
            product 연결 상품 정보
            youtube_url 유튜브 url

        g.account_info: 데코레이터에서 넘겨받은 수정을 수행하는 계정 정보
            auth_type_id: 계정의 권한정보
            account_no: 데코레이터에서 확인된 계정번호

        Returns: http 응답코드
            200: SUCCESS 수정(새로운 이력 생성) 완료
            400: NOT_ALLOWED_TO_CHANGE_EVENT_TYPE_OR_SORT, INVALID_EVENT_NO
                 NO_SHORT_DESCRIPTION, NO_BANNER_IMAGE, NO_DETAIL_IMAGE,
                 INVALID_EVENT_SORT, NO_YOUTUBE_URL, NO_BUTTON_NAME, NO_BUTTON_LINK_DESCRIPTION
            500: DB_CURSOR_ERROR, INVALID_KEY, NO_DATABASE_CONNECTION

        Authors:
            [email protected] (이종민)
            [email protected] (윤희철)

        History:
            2020-04-10 ([email protected]): 초기 생성
            2020-04-11 ([email protected]):
                - utils.py(이미지 업로더) 에서 나오는 결과값에 애러코드 400이있으면 애러메세지를 그대로 리턴하는 코드 추가
                - 기획전 상품이 validation 을 통과하면 json loads 를 통해서 array 자료형으로 파싱하는 코드 추가.
                - 기획전 상품이 들어오면 따로 변수처리해서 service 로 넘기는 코드 추가
                - 데이터베이스 커넥션을 호출 할 때 try/except 방식 추가
            2020-04-12 ([email protected]):
                - 기획전용 이미지 업로더를 사용하는 것에서 공통 업로더를 사용하도록 변경
                - 기획전 상품 정보를 json loads 로 파싱하는 과정을 try/except 방식에서 if 문 방식으로 변경
                - 기획전 타입이 상품(텍스트), 유튜브 일 경우 기획전 종류 유효성 확인 추가
            2020-04-15 ([email protected]:
                - button_link_type_id 를 str 로 받던 것에서 int 로 받기로 변경
        """

        # 마스터 권한이 아니면 반려
        if g.account_info['auth_type_id'] != 1:
            return jsonify({'message': 'NO_AUTHORIZATION'}), 403

        # 이미지 업로드 함수를 호출해서 이미지를 업로드하고 url 을 딕셔너리로 가져옴.
        image_upload = ImageUpload()
        event_image = image_upload.upload_images(request)

        # 함수의 실행결과에 400이 포함된 경우 애러메세지를 그대로 리턴함.
        if (400 in event_image) or (500 in event_image):
            return event_image

        # validation(형식) 확인된 데이터 저장
        event_info = {
            'event_no': args[0],
            'event_type_id': args[1],
            'event_sort_id': args[2],
            'is_on_main': args[3],
            'is_on_event': args[4],
            'name': args[5],
            'event_start_time': args[6],
            'event_end_time': args[7],
            'short_description': args[8],
            'long_description': args[9],
            'banner_image_url': event_image.get('banner_image', None),
            'detail_image_url': event_image.get('detail_image', None),
            'button_name': args[14],
            'button_link_type_id': args[15],
            'button_link_description': args[16],
            'youtube_url': args[18],
            'auth_type_id': g.account_info['auth_type_id'],
            'account_no': g.account_info['account_no']
        }

        # file 로 이미지가 안들어올 경우, FORM 으로 받은 이미지 url 로 대체
        if not event_info['banner_image_url']:
            event_info['banner_image_url'] = args[10]

        if not event_info['detail_image_url']:
            event_info['detail_image_url'] = args[12]

        # 딕셔너리를 품은 리스트인 product 정보를 따로 저장 (dao 에서 에러를 막기 위해)
        event_product_info = args[17]

        # form 데이터로 값을 받으면 str 처리가 되기 때문에 json.loads 로 읽을 수 있게 파싱
        if event_product_info:
            event_product_info = json.loads(event_product_info)

        # 기획전 기간 밸리데이션
        now = datetime.strftime(datetime.now(), '%Y-%m-%d %H:%M')

        # 시작시간이 현재 시간보다 전이거나 시작시간이 종료시간보다 늦으면 에러 반환
        if event_info['event_start_time'] < now or event_info[
                'event_start_time'] > event_info['event_end_time']:
            return jsonify({'message': 'INVALID_EVENT_TIME'}), 400

        # 기획전 타입이 이벤트일 경우 필수값과 기획전 종류 범위 확인
        if event_info['event_type_id'] == 1:
            if not event_info['short_description']:
                return jsonify({'message': 'NO_SHORT_DESCRIPTION'}), 400

            if not event_info['banner_image_url']:
                return jsonify({'message': 'NO_BANNER_IMAGE'}), 400

            if not event_info['detail_image_url']:
                return jsonify({'message': 'NO_DETAIL_IMAGE'}), 400

            # 기획전 종류 범위 확인
            if event_info['event_sort_id'] not in range(1, 3):
                return jsonify({'message': 'INVALID_EVENT_SORT'}), 400

        # 기획전 타입이 쿠폰일 경우 필수값 확인
        if event_info['event_type_id'] == 2:
            if not event_info['short_description']:
                return jsonify({'message': 'NO_SHORT_DESCRIPTION'}), 400

            # 기획전 종류 범위 확인
            if event_info['event_sort_id'] not in range(3, 9):
                return jsonify({'message': 'INVALID_EVENT_SORT'}), 400

        # 기획전 타입이 상품(이미지)일 경우 필수값과 기획전 종류 범위 확인
        if event_info['event_type_id'] == 3:
            if not event_info['banner_image_url']:
                return jsonify({'message': 'NO_BANNER_IMAGE'}), 400

            if not event_info['detail_image_url']:
                return jsonify({'message': 'NO_DETAIL_IMAGE'}), 400

            # 기획전 종류 범위 확인
            if event_info['event_sort_id'] not in range(9, 11):
                return jsonify({'message': 'INVALID_EVENT_SORT'}), 400

        # 기획전 타입이 상품(텍스트)일 경우 필수값과 기획전 종류 범위 확인
        if event_info['event_type_id'] == 4:
            if not event_info['short_description']:
                return jsonify({'message': 'NO_SHORT_DESCRIPTION'}), 400

            if not event_info['banner_image_url']:
                return jsonify({'message': 'NO_BANNER_IMAGE'}), 400

            # 기획전 종류 범위 확인
            if event_info['event_sort_id'] not in range(11, 13):
                return jsonify({'message': 'INVALID_EVENT_SORT'}), 400

        # 기획전 타입이 유튜브일 경우 필수값 확인과 기획전 종류 범위 확인
        if event_info['event_type_id'] == 5:
            if not event_info['short_description']:
                return jsonify({'message': 'NO_SHORT_DESCRIPTION'}), 400

            if not event_info['banner_image_url']:
                return jsonify({'message': 'NO_BANNER_IMAGE'}), 400

            if not event_info['youtube_url']:
                return jsonify({'message': 'NO_YOUTUBE_URL'}), 400

            # 기획전 종류 범위 확인
            if event_info['event_sort_id'] not in range(13, 15):
                return jsonify({'message': 'INVALID_EVENT_SORT'}), 400

        # 입력 인자 관계에 따른 필수값 확인
        if event_info['button_link_type_id']:
            if not event_info['button_name']:
                return jsonify({'message': 'NO_BUTTON_NAME'}), 400

            if event_info['button_link_type_id'] in range(4, 7):
                if not event_info['button_link_description']:
                    return jsonify({'message':
                                    'NO_BUTTON_LINK_DESCRIPTION'}), 400

        try:
            db_connection = get_db_connection()
            if db_connection:
                event_service = EventService()
                info = event_service.change_event_infos(
                    event_info, event_product_info, db_connection)
                return info

            else:
                return jsonify({'message': 'NO_DATABASE_CONNECTION'}), 500

        except Exception as e:
            return jsonify({'message': f'{e}'}), 500

        finally:
            try:
                db_connection.close()

            except Exception as e:
                return jsonify({'message': f'{e}'}), 500