From c96a75f402fc62f7e6d663870f210bb9a563d918 Mon Sep 17 00:00:00 2001 From: App Generator <51070104+app-generator@users.noreply.github.com> Date: Sat, 7 Dec 2024 12:06:53 +0200 Subject: [PATCH] Added API via Flask-RestX --- apps/__init__.py | 2 +- apps/api/.gitkeep | 0 apps/api/__init__.py | 12 ++ apps/api/controller/base_contoller.py | 85 +++++++++ apps/api/controller/product_controller.py | 114 ++++++++++++ apps/api/controller/sale_controller.py | 114 ++++++++++++ apps/api/exception.py | 15 ++ apps/api/routes.py | 19 ++ apps/api/schemas/product_schema.py | 27 +++ apps/api/schemas/sale_schema.py | 38 ++++ apps/api/service/product_service.py | 62 ++++++ apps/api/service/sale_service.py | 94 ++++++++++ apps/config.py | 9 + apps/db.sqlite3 | Bin 28672 -> 36864 bytes apps/exceptions/exception.py | 15 ++ apps/helpers.py | 176 ++++++++++++++++++ apps/messages.py | 35 ++++ apps/models.py | 102 ++++++++++ .../{b552b1e60608_.py => d74b189c022b_.py} | 35 +++- requirements.txt | 3 + 20 files changed, 953 insertions(+), 4 deletions(-) delete mode 100644 apps/api/.gitkeep create mode 100644 apps/api/__init__.py create mode 100644 apps/api/controller/base_contoller.py create mode 100644 apps/api/controller/product_controller.py create mode 100644 apps/api/controller/sale_controller.py create mode 100644 apps/api/exception.py create mode 100644 apps/api/routes.py create mode 100644 apps/api/schemas/product_schema.py create mode 100644 apps/api/schemas/sale_schema.py create mode 100644 apps/api/service/product_service.py create mode 100644 apps/api/service/sale_service.py create mode 100644 apps/exceptions/exception.py create mode 100644 apps/helpers.py create mode 100644 apps/messages.py create mode 100644 apps/models.py rename migrations/versions/{b552b1e60608_.py => d74b189c022b_.py} (51%) diff --git a/apps/__init__.py b/apps/__init__.py index 045fb89..a458b27 100644 --- a/apps/__init__.py +++ b/apps/__init__.py @@ -21,7 +21,7 @@ def register_extensions(app): def register_blueprints(app): - for module_name in ('authentication', 'home'): + for module_name in ('authentication', 'home', 'api'): module = import_module('apps.{}.routes'.format(module_name)) app.register_blueprint(module.blueprint) diff --git a/apps/api/.gitkeep b/apps/api/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/apps/api/__init__.py b/apps/api/__init__.py new file mode 100644 index 0000000..bdfcb3b --- /dev/null +++ b/apps/api/__init__.py @@ -0,0 +1,12 @@ +# -*- encoding: utf-8 -*- +""" +Copyright (c) 2019 - present AppSeed.us +""" + +from flask import Blueprint + +blueprint = Blueprint( + 'api_blueprint', + __name__, + url_prefix='/api' +) diff --git a/apps/api/controller/base_contoller.py b/apps/api/controller/base_contoller.py new file mode 100644 index 0000000..024a252 --- /dev/null +++ b/apps/api/controller/base_contoller.py @@ -0,0 +1,85 @@ +import flask +import traceback +from flask import jsonify +from apps.api.exception import InvalidUsage + +app = flask.current_app + +class BaseController: + + # Set Json response + def send_response(self, data, status_code=200): + res = { + "data": data, + 'status': status_code + } + resp = jsonify(res) + return resp + + + # Set success response. + def success(self, data, message ="", code =200): + """form final respose format + + Args: + data (dict/list): for single record it would be dictionary and for multiple records it would be list. + message (str): operation response message. Defaults to "". + code (int): Http response code. Defaults to 200. + + Returns: + dictionary of response data + { + data: [], + message: "", + status: 200, + } + """ + res = { + "data": data, + "message": message, + 'status': code + } + resp = jsonify(res) + return resp + + # Set error response. + def error(self, e, code=422): + msg = str(e) + if isinstance(e, InvalidUsage): + msg = e.message + + # app.logger.error(traceback.format_exc()) + res = { + # 'error':{ + # 'description': traceback.format_exc() + # }, + "message": msg, + 'status': code + } + return res, code + + + def errorGeneral(self, e, code=422): + msg = str(e) + if isinstance(e, InvalidUsage): + msg = e.message + res = { + "message": msg, + 'status': code + } + return res, code + # msg = 'An exception occurred: {}'.format(error) + # res = { + # 'errors':msg, + # 'status': code + # } + # return res + + + def simple_response(self, message, status_code): + res = { + "message": message, + 'status': status_code + } + resp = jsonify(res) + return resp diff --git a/apps/api/controller/product_controller.py b/apps/api/controller/product_controller.py new file mode 100644 index 0000000..4118efb --- /dev/null +++ b/apps/api/controller/product_controller.py @@ -0,0 +1,114 @@ +from flask import request +from flask_restx import Resource +from apps.api.exception import InvalidUsage +from apps.models import Product +from apps.api.schemas.product_schema import ProductSchema, ProductUpdateSchema +from apps.api.controller.base_contoller import BaseController +from apps.api.service.product_service import ProductService +from apps.helpers import token_required +from apps.messages import Messages +message = Messages.message + +# base controller +BaseController = BaseController() +# schemas +product_schema = ProductSchema() +products_schema = ProductSchema(many=True) +product_update_schema = ProductUpdateSchema() +# services +product_service = ProductService() + + +class ProductCreateList(Resource): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + @classmethod + @token_required + def post(self): + try: + request_data = request.json + # validate data + product_schema.load(request_data) + product = product_service.create(self.id, request_data) + data = product_schema.dump(product) + + return BaseController.success(data, message['record_created_successfully']) + except InvalidUsage as e: + return BaseController.error(e, 422) + except BaseException as e: + return BaseController.errorGeneral(e) + + @classmethod + def get(self): + try: + products = Product.query.order_by(Product.id.desc()).all() + response = products_schema.dump(products) + + return BaseController.send_response(response, 200) + except InvalidUsage as e: + return BaseController.error(e, 422) + except BaseException as e: + return BaseController.errorGeneral(e) + + + +class ProductList(Resource): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + @classmethod + def get(self, id): + try: + result = Product.find_by_id(id) + # check record exists or not + if result is None: + return BaseController.error(message['not_exists'], 422) + + response = product_schema.dump(result) + + return BaseController.send_response(response, 200) + except InvalidUsage as e: + return BaseController.error(e, 422) + except BaseException as e: + return BaseController.errorGeneral(e) + + @classmethod + @token_required + def put(self, id): + try: + request_data = request.json + product = Product.find_by_id(id) + # check record exists or not + if product is None: + return BaseController.error(message['not_exists'], 422) + + # validate data + product_update_schema.load(request_data) + product = product_service.update(self.id, id, request_data) + data = product_update_schema.dump(product) + + return BaseController.success(data, message['record_updated'], 200) + except InvalidUsage as e: + return BaseController.error(e, 422) + except BaseException as e: + return BaseController.errorGeneral(e) + + @classmethod + @token_required + def delete(self, id): + try: + product = Product.find_by_id(id) + # check record exists or not + if product is None: + return BaseController.error(message['not_exists'], 422) + + product.delete() + + return BaseController.simple_response(message['deleted_successfully'], 200) + except InvalidUsage as e: + return BaseController.error(e, 422) + except BaseException as e: + return BaseController.errorGeneral(e) diff --git a/apps/api/controller/sale_controller.py b/apps/api/controller/sale_controller.py new file mode 100644 index 0000000..bf21fba --- /dev/null +++ b/apps/api/controller/sale_controller.py @@ -0,0 +1,114 @@ +from flask_restx import Resource +from flask import request +from apps.api.exception import InvalidUsage +from apps.models import Sale +from apps.api.schemas.sale_schema import SaleSchema, SaleUpdateSchema +from apps.api.controller.base_contoller import BaseController +from apps.api.service.sale_service import SaleService +from apps.helpers import token_required +from apps.messages import Messages +message = Messages.message + +# base controller +BaseController = BaseController() +# schemas +sale_schema = SaleSchema() +sales_schema = SaleSchema(many=True) +sale_update_schema = SaleUpdateSchema() +# services +sale_service = SaleService() + + +class SaleCreateList(Resource): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + @classmethod + @token_required + def post(self): + try: + request_data = request.json + # validate data + sale_schema.load(request_data) + sale = sale_service.create(self.id, request_data) + data = sale_schema.dump(sale) + + return BaseController.success(data, message['record_created_successfully'], 201) + except InvalidUsage as e: + return BaseController.error(e, 422) + except BaseException as e: + return BaseController.errorGeneral(e) + + @classmethod + def get(self): + try: + sales = Sale.query.order_by(Sale.id.desc()).all() + response = sales_schema.dump(sales) + + return BaseController.send_response(response, 200) + except InvalidUsage as e: + return BaseController.error(e, 422) + except BaseException as e: + return BaseController.errorGeneral(e) + + + +class SaleList(Resource): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + @classmethod + def get(self, id): + try: + result = Sale.find_by_id(id) + # check record exists or not + if result is None: + return BaseController.error(message['not_exists'], 422) + + response = sale_schema.dump(result) + + return BaseController.send_response(response, 200) + except InvalidUsage as e: + return BaseController.error(e, 422) + except BaseException as e: + return BaseController.errorGeneral(e) + + @classmethod + @token_required + def put(self, id): + try: + request_data = request.json + sale = Sale.find_by_id(id) + # check record exists or not + if sale is None: + return BaseController.error(message['not_exists'], 422) + + # validate data + sale_update_schema.load(request_data) + sale = sale_service.update(id, request_data) + data = sale_update_schema.dump(sale) + + return BaseController.success(data, message['record_updated'], 200) + except InvalidUsage as e: + return BaseController.error(e, 422) + except BaseException as e: + return BaseController.errorGeneral(e) + + @classmethod + @token_required + def delete(self, id): + try: + sale = Sale.find_by_id(id) + # check record exists or not + if sale is None: + return BaseController.error(message['not_exists'], 422) + + sale.delete() + + return BaseController.send_response(message['deleted_successfully'], 200) + except InvalidUsage as e: + return BaseController.error(e, 422) + except BaseException as e: + return BaseController.errorGeneral(e) diff --git a/apps/api/exception.py b/apps/api/exception.py new file mode 100644 index 0000000..6e3ab02 --- /dev/null +++ b/apps/api/exception.py @@ -0,0 +1,15 @@ +class InvalidUsage(Exception): + status_code = 400 + + def __init__(self, message, status_code=None, payload=None): + Exception.__init__(self) + self.message = message + if status_code is not None: + self.status_code = status_code + self.payload = payload + + def to_dict(self): + rv = dict(self.payload or ()) + rv['message'] = self.message + + return rv diff --git a/apps/api/routes.py b/apps/api/routes.py new file mode 100644 index 0000000..5d44223 --- /dev/null +++ b/apps/api/routes.py @@ -0,0 +1,19 @@ +# -*- encoding: utf-8 -*- +""" +Copyright (c) 2019 - present AppSeed.us +""" +from apps.api import blueprint +from apps.api.controller.product_controller import ProductCreateList, ProductList +from apps.api.controller.sale_controller import SaleCreateList, SaleList +from flask_restx import Api + +# from flask_restx import Api +api = Api(blueprint, title="API", description="API") + +# Product api's end points. +api.add_resource(ProductCreateList, '/products/') +api.add_resource(ProductList, '/products/') + +# Sale api's end points. +api.add_resource(SaleCreateList, '/sales/') +api.add_resource(SaleList, '/sales/') \ No newline at end of file diff --git a/apps/api/schemas/product_schema.py b/apps/api/schemas/product_schema.py new file mode 100644 index 0000000..8fe1200 --- /dev/null +++ b/apps/api/schemas/product_schema.py @@ -0,0 +1,27 @@ +# Model Schemas +from ...models import Product +from marshmallow import fields +from marshmallow_sqlalchemy import ModelSchema +from apps import db + + +class ProductSchema(ModelSchema): + class Meta(ModelSchema.Meta): + fields = ("id", "name", "information", "description", "price", "currency") + model = Product + sqla_session = db.session + name = fields.String(strict=True, required=True) + information = fields.String(strict=True, required=True) + price = fields.Integer(strict=True, required=True) + + +class ProductUpdateSchema(ModelSchema): + class Meta(ModelSchema.Meta): + fields = ("id", "name", "information", "description", "price", "currency") + model = Product + # strict = True + sqla_session = db.session + name = fields.String(strict = True, required=True) + information = fields.String(strict = True, required=True) + price = fields.Integer(strict=True, required=True) + currency = fields.String(strict = True, required=True) diff --git a/apps/api/schemas/sale_schema.py b/apps/api/schemas/sale_schema.py new file mode 100644 index 0000000..e779e14 --- /dev/null +++ b/apps/api/schemas/sale_schema.py @@ -0,0 +1,38 @@ +# Model Schemas +from ...models import Sale +from marshmallow import fields +from marshmallow_sqlalchemy import ModelSchema +from apps import db + + +class SaleSchema(ModelSchema): + class Meta(ModelSchema.Meta): + fields = ("id", "product", "state", "value", "fee", "currency", "client", "payment_type", "purchase_date") + model = Sale + # strict = True + sqla_session = db.session + product = fields.Integer(strict=True, required=True) + state = fields.String(strict=True, required=True) + value = fields.Integer(strict=True, required=True) + fee = fields.Integer(strict=True, required=False) + payment_type = fields.String(strict=True, required=False) + client = fields.String(strict=True, required=False) + purchase_date = fields.String(strict=True, required=False) + # user_id = fields.Integer(required=False) + + +class SaleUpdateSchema(ModelSchema): + class Meta(ModelSchema.Meta): + fields = ("id", "product", "state", "value", "fee", "currency", "client", "payment_type", "purchase_date") + model = Sale + # strict = True + sqla_session = db.session + product = fields.Integer(strict = True, required=True) + state = fields.String(strict = True, required=True) + value = fields.Integer(strict = True, required=True) + fee = fields.Integer(strict = True, required=True) + currency = fields.String(strict = True, required=True) + client = fields.String(strict = True, required=True) + payment_type = fields.String(strict = True, required=True) + # purchase_date = fields.String(strict = True, required=True) + # user_id = fields.Integer(required=False) diff --git a/apps/api/service/product_service.py b/apps/api/service/product_service.py new file mode 100644 index 0000000..6b60d96 --- /dev/null +++ b/apps/api/service/product_service.py @@ -0,0 +1,62 @@ +from flask_restx import Resource +from apps.models import Product +from marshmallow import fields, ValidationError +from apps.authentication.models import Users +from apps.helpers import validateCurrency +from apps.messages import Messages +from apps.config import Config + +Currency = Config.CURRENCY +message = Messages.message + + +class ProductService(Resource): + + def create(self, user_id, request_data): + name = request_data['name'] + information = request_data['information'] + description = request_data['description'] + price = request_data['price'] + + # user not none + if user_id is not None: + # check user + self.checkUser(user_id) + + new_product = Product( + user_id = user_id, + name = name, + information = information, + description = description, + price = price + ) + new_product.save() + return new_product + + def update(self,user_id, id, request_data): + currency = request_data['currency'] + + # user not none + if user_id is not None: + # check user + self.checkUser(user_id) + + # check currency + validateCurrency(currency) + + product = Product.find_by_id(id) + product.name = request_data['name'] + product.information = request_data['information'] + product.description = request_data['description'] + product.price = request_data['price'] + product.currency = currency + + product.save() + return product + + def checkUser(self, user_id): + """ check user """ + user = Users.find_by_id(user_id) + # check user + if not user: + raise ValidationError(message['user_not_found'], 422) \ No newline at end of file diff --git a/apps/api/service/sale_service.py b/apps/api/service/sale_service.py new file mode 100644 index 0000000..0bdb211 --- /dev/null +++ b/apps/api/service/sale_service.py @@ -0,0 +1,94 @@ +from flask_restx import Resource +from apps.models import Product, Sale +from marshmallow import ValidationError +from apps.authentication.models import Users +from apps.helpers import validateCurrency, validatePaymentMethod, validateState +from apps.messages import Messages + +message = Messages.message + + +class SaleService(Resource): + + def create(self, user_id, request_data): + product = request_data.get('product') + value = request_data.get('value') + currency = request_data.get('currency') + fee = request_data.get('fee') + client = request_data.get('client') + + # user not none + if user_id is not None: + # check user + self.checkUser(user_id) + else: + raise ValidationError("user id required", 422) + + # check state + state = validateState(request_data.get('state')) + # check payment method + payment_type = validatePaymentMethod(request_data.get('payment_type')) + # check currency + validateCurrency(currency) + + result = Product.find_by_id(product) + # check record exists or not + if result is None: + raise ValidationError(message['product_not_exists'], 422) + + new_sale = Sale( + product = product, + state = state, + value = value, + fee = fee, + currency = currency, + client = client, + payment_type = payment_type, + ) + + new_sale.save() + return new_sale + + def update(self, id, request_data): + product = request_data.get('product') + value = request_data.get('value') + currency = request_data.get('currency') + fee = request_data.get('fee') + client = request_data.get('client') + + user_id = request_data.get('user_id') + # user not none + if user_id is not None: + # check user + self.checkUser(user_id) + + # check state + state = validateState(request_data.get('state')) + # check payment method + payment_type = validatePaymentMethod(request_data.get('payment_type')) + # check currency + validateCurrency(currency) + + result = Product.find_by_id(product) + # check record exists or not + if result is None: + raise ValidationError(message['product_not_exists'], 422) + + sale = Sale.find_by_id(id) + sale.product = product + sale.state = state + sale.value = value + sale.fee = fee + sale.currency = currency + sale.client = client + sale.payment_type = payment_type + + sale.save() + return sale + + def checkUser(self, user_id): + """ check user """ + user = Users.find_by_id(user_id) + # check user + if not user: + raise ValidationError(message['user_not_found'], 422) \ No newline at end of file diff --git a/apps/config.py b/apps/config.py index 69292d6..ee708d1 100644 --- a/apps/config.py +++ b/apps/config.py @@ -9,6 +9,15 @@ class Config(object): basedir = os.path.abspath(os.path.dirname(__file__)) + + # for Product model + CURRENCY = { 'usd' : 'usd' , 'eur' : 'eur' } + STATE = { 'completed' : 1 , 'pending' : 2, 'refunded' : 3 } + PAYMENT_TYPE = { 'cc' : 1 , 'paypal' : 2, 'wire' : 3 } + + USERS_ROLES = { 'ADMIN' :1 , 'USER' : 2 } + USERS_STATUS = { 'ACTIVE' :1 , 'SUSPENDED' : 2 } + # Assets Management ASSETS_ROOT = os.getenv('ASSETS_ROOT', '/static/assets') diff --git a/apps/db.sqlite3 b/apps/db.sqlite3 index a8b16b3a63013ec7182f3e3d3a3cd245c1bab8eb..3948d604e6da6bed9d180c29af072f20caf356b2 100644 GIT binary patch delta 1138 zcmah}O=}ZD7~WNzCN;~Zh(@WUBce2*VRm+Qccvg3>#D^zN{pYEI$x`cZ4x$#P*Cd^ zUc9KmyLb>i>R$BZkMIw869hf=(4(8xs;LzhW(S^U-sgSa=Vj*3C;iUzO^?S$vzn&m zlC|~?Jvm^HXSH(h#V}X3Y_6r5hWXz3n6DVGj9Xg^xz?MzE7|h>@$7K1Skxcx>M*{b z+ALY=FE?HDD|H{#D@SL2kV0Ux_b$JCS$0JS%v9_C3BLxar|Y14Zg%zn7|FFetfMw$ zZm@;!#!R4o;e83wjUv?$OW=H^Hhr>Eo3vnQAct634ppP`n@6P74{Wie#RQ-4EVX_K z4g|Kkk+{a%YF_q>JeIUppPBR1oRMOku-W*V>9+cZ*WtbW#@?}3)a=_(0yY1*U-PTe z{#o!7>oH?&Q+c>ImoMwt_-0<$^)0>I#twZberD{|hccPspmGWWdA!^QD&{Xr!i%1`0;+P-CAG;3*J8en-olxzO?J8Q5-HQ>~MBVi+GMvH0d z|2`yDTSQ^2?+>W^7wf6lQ7ASv`Rf1Yml`Vijw?AZg_Kxq$}kAmF=uKsbCud9aLT^~ zCPP^oDaD5QxsaD0<`$s>BE;W<6?4a*fRWq--HbIejz6C+~5H5Srp{}=%ai$#jLwrIrClc}PW^dx~o@Gy1 z%7r|sNH9W(EttYoxmY;@OT{tw0_-7fLGExjkdESzP)Z2o)R71whN+DMj68*Ghfo#> z=(v(gq5_GzQjpn>u%yk2>nY+9mvN5-j>RnLK@rSY#1fR-PGE7YEQpmv5~3s;Dax@8 z1Be~r5~`S@mT)Qcpt6$6sFIK&PRU5>*jFts9C>6ud3?F|^>i_#CvS(j8joK2X2z@Y J*}{t}h41iAIc@*| delta 1473 zcmZ8hyKkg55Z}#aFNDqJOLRy9A#psKL&rt-*kg|+3PhO(0R=)7IomT+A#ox(QBdrK zqoD&uS6#z@fJ6#JL&HCCqC!*%AyFL_MM`G#kfMAK+nUecjDHXN;rA;K{~Uk&^7i^@ zG+MLw@|t}2R(NTBwENNCd^jE5*w`Bl^Wo?Dmz#fW{d7Y?Z;Z#&4wn{UrQ-uPnn*or@zEspl5v(e6Sxpy9Ij1J}C zFr78;)cf}jM0Ot>v*iP2c(+$gjPK-i07 zhGxV9#9Ctu&P&CfyJH`L`|>VLLNZcXO0Hv!;?xK@jC6RQX@VJKQSRCd@b2gDpgg-#)~CN3V`n_OVLdm&?3Vhk5D zvgt^*^iobQoL90)>6T)1ax#NK%;W`rgVoa`xyBt*~ZxVjXn z`dplY6iPNDIG=O&&L$`l7%@$~7c82&BnZuA^oXdXHB$A>FFg9vjUw(fiebZeR+KeJ z?cR{rl46h)Nb%_2$~BO%1PNC_mKsb&C#HOEkimg)<)ulKssK3Z$tS}koicP?B-SLM zc&MU1ho~CJWKJPS?mbuYyKyBwQ!;0#mXT`$jLqzV*0Y%%#Qa`b5Y-kbyJ#L)W6G$O z#JMy`6pJKb!0f9*5sNj5E^SLS)Ugp6T3u>$eCxf}7QPp3s#VoRf`;}}qntFSLYAni zLv5HSv$#;mc}^*p&^^VPsbry$8=Kpz^9a-u`feGFheEvs*K@8?s&vh^Uh0*6q2{#^ zlUeM;QgZFaNJTSs^_5(+?Qf!HMHENkNex{0XewqVFfXXfUZF9VqxJdaaWgy_4Zjb+ z4nGV}hG%x%d^3D-b8=^V?R=SD#?Ch6`F>n|VRH9u-c "Product": + return cls.query.filter_by(id=_id).first() + + def save(self) -> None: + try: + db.session.add(self) + db.session.commit() + except SQLAlchemyError as e: + db.session.rollback() + db.session.close() + error = str(e.__dict__['orig']) + raise InvalidUsage(error, 422) + + def delete(self) -> None: + try: + db.session.delete(self) + db.session.commit() + except SQLAlchemyError as e: + db.session.rollback() + db.session.close() + error = str(e.__dict__['orig']) + raise InvalidUsage(error, 422) + return + + +class Sale(db.Model): + + __tablename__ = 'sales' + + id = db.Column(db.Integer, primary_key=True) + product = db.Column(db.Integer, db.ForeignKey("products.id", ondelete="cascade"), nullable=False) + product_id = relationship(Product, uselist=False, backref="sales") + state = db.Column(db.Integer, nullable=False) + value = db.Column(db.Integer, nullable=False) + fee = db.Column(db.Integer, default=0) + currency = db.Column(db.String(10), default=Currency['usd'], nullable=False) + client = db.Column(db.String(128), nullable=True) + payment_type = db.Column(db.Integer(), default=PAYMENT_TYPE['cc'], nullable=False) + purchase_date = db.Column(db.DateTime, default=dt.datetime.utcnow()) + creation_date = db.Column(db.DateTime, default=dt.datetime.utcnow()) + update_date = db.Column(db.DateTime, default=db.func.current_timestamp(), + onupdate=db.func.current_timestamp()) + + @classmethod + def find_by_id(cls, _id: int) -> "Sale": + return cls.query.filter_by(id=_id).first() + + def save(self) -> None: + try: + db.session.add(self) + db.session.commit() + except SQLAlchemyError as e: + db.session.rollback() + db.session.close() + error = str(e.__dict__['orig']) + raise InvalidUsage(error, 422) + + def delete(self) -> None: + try: + db.session.delete(self) + db.session.commit() + except SQLAlchemyError as e: + db.session.rollback() + db.session.close() + error = str(e.__dict__['orig']) + raise InvalidUsage(error, 422) + return diff --git a/migrations/versions/b552b1e60608_.py b/migrations/versions/d74b189c022b_.py similarity index 51% rename from migrations/versions/b552b1e60608_.py rename to migrations/versions/d74b189c022b_.py index fdcab9c..c2839f0 100644 --- a/migrations/versions/b552b1e60608_.py +++ b/migrations/versions/d74b189c022b_.py @@ -1,8 +1,8 @@ """empty message -Revision ID: b552b1e60608 +Revision ID: d74b189c022b Revises: -Create Date: 2024-11-29 16:25:51.697299 +Create Date: 2024-12-07 11:48:43.910944 """ from alembic import op @@ -10,7 +10,7 @@ # revision identifiers, used by Alembic. -revision = 'b552b1e60608' +revision = 'd74b189c022b' down_revision = None branch_labels = None depends_on = None @@ -18,6 +18,18 @@ def upgrade(): # ### commands auto generated by Alembic - please adjust! ### + op.create_table('products', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('user_id', sa.Integer(), nullable=True), + sa.Column('name', sa.String(length=128), nullable=False), + sa.Column('information', sa.String(length=128), nullable=False), + sa.Column('description', sa.Text(), nullable=True), + sa.Column('price', sa.Integer(), nullable=False), + sa.Column('currency', sa.String(length=10), nullable=False), + sa.Column('date_created', sa.DateTime(), nullable=True), + sa.Column('date_modified', sa.DateTime(), nullable=True), + sa.PrimaryKeyConstraint('id') + ) op.create_table('users', sa.Column('id', sa.Integer(), nullable=False), sa.Column('username', sa.String(length=64), nullable=True), @@ -41,11 +53,28 @@ def upgrade(): sa.ForeignKeyConstraint(['user_id'], ['users.id'], ondelete='cascade'), sa.PrimaryKeyConstraint('id') ) + op.create_table('sales', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('product', sa.Integer(), nullable=False), + sa.Column('state', sa.Integer(), nullable=False), + sa.Column('value', sa.Integer(), nullable=False), + sa.Column('fee', sa.Integer(), nullable=True), + sa.Column('currency', sa.String(length=10), nullable=False), + sa.Column('client', sa.String(length=128), nullable=True), + sa.Column('payment_type', sa.Integer(), nullable=False), + sa.Column('purchase_date', sa.DateTime(), nullable=True), + sa.Column('creation_date', sa.DateTime(), nullable=True), + sa.Column('update_date', sa.DateTime(), nullable=True), + sa.ForeignKeyConstraint(['product'], ['products.id'], ondelete='cascade'), + sa.PrimaryKeyConstraint('id') + ) # ### end Alembic commands ### def downgrade(): # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('sales') op.drop_table('flask_dance_oauth') op.drop_table('users') + op.drop_table('products') # ### end Alembic commands ### diff --git a/requirements.txt b/requirements.txt index 17ec986..457c2b1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -16,6 +16,9 @@ flask-restx==1.3.0 flask-dance==7.1.0 celery==5.4.0 redis==5.2.1 +marshmallow==3.14.1 +marshmallow-sqlalchemy==0.22.3 +colorama==0.4.4 # utils email_validator==2.2.0