From 7556d1a75cd38b20545cb642ff1b6a073b2e5837 Mon Sep 17 00:00:00 2001 From: amnesia Date: Wed, 28 Feb 2024 16:08:07 -0500 Subject: [PATCH] first commit --- Dockerfile | 22 ++++ README.MD | 5 + app.py | 13 +++ app/__init__.py | 186 ++++++++++++++++++++++++++++++++++ app/auth/__init__.py | 8 ++ app/auth/views.py | 42 ++++++++ app/classes/__init__.py | 0 app/classes/auth.py | 79 +++++++++++++++ app/classes/company.py | 25 +++++ app/classes/customer.py | 81 +++++++++++++++ app/classes/delivery.py | 113 +++++++++++++++++++++ app/classes/employee.py | 73 +++++++++++++ app/classes/oil.py | 25 +++++ app/classes/query.py | 70 +++++++++++++ app/classes/service.py | 127 +++++++++++++++++++++++ app/classes/stats_customer.py | 29 ++++++ app/classes/stats_employee.py | 46 +++++++++ app/common/__init__.py | 0 app/common/decorators.py | 15 +++ app/create/__init__.py | 0 app/create/views.py | 41 ++++++++ app/info/__init__.py | 7 ++ app/info/views.py | 17 ++++ app/main/__init__.py | 7 ++ app/main/views.py | 31 ++++++ app/pay/__init__.py | 7 ++ app/pay/views.py | 63 ++++++++++++ app/update/__init__.py | 7 ++ app/update/views.py | 73 +++++++++++++ local_settings.py | 64 ++++++++++++ requirements.txt | 22 ++++ runProduction.py | 11 ++ 32 files changed, 1309 insertions(+) create mode 100755 Dockerfile create mode 100755 README.MD create mode 100755 app.py create mode 100644 app/__init__.py create mode 100644 app/auth/__init__.py create mode 100644 app/auth/views.py create mode 100644 app/classes/__init__.py create mode 100644 app/classes/auth.py create mode 100644 app/classes/company.py create mode 100644 app/classes/customer.py create mode 100644 app/classes/delivery.py create mode 100644 app/classes/employee.py create mode 100644 app/classes/oil.py create mode 100644 app/classes/query.py create mode 100644 app/classes/service.py create mode 100644 app/classes/stats_customer.py create mode 100644 app/classes/stats_employee.py create mode 100644 app/common/__init__.py create mode 100644 app/common/decorators.py create mode 100644 app/create/__init__.py create mode 100644 app/create/views.py create mode 100644 app/info/__init__.py create mode 100644 app/info/views.py create mode 100644 app/main/__init__.py create mode 100644 app/main/views.py create mode 100644 app/pay/__init__.py create mode 100644 app/pay/views.py create mode 100644 app/update/__init__.py create mode 100644 app/update/views.py create mode 100755 local_settings.py create mode 100755 requirements.txt create mode 100755 runProduction.py diff --git a/Dockerfile b/Dockerfile new file mode 100755 index 0000000..44ab5c9 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,22 @@ +FROM python:3.12-bullseye + +ENV PYTHONFAULTHANDLER=1 + +ENV PYTHONUNBUFFERED=1 + +ENV STRIPE_PUBLIC_KEY = pk_test_51OUbSMJznCGgUo9krwqaJkCtdnROJ2gyTcUWQGOHcaREDqP8dPGhMmLTbI1sFiyiKiK3BOPasTayBnFFth0pb81g00qlPdABbC + +ENV STRIPE_SECRET_KEY = sk_test_51OUbSMJznCGgUo9kWM2Uv0UjM0Ai6etCOOHVKkgFBVxO66VtIqlOFL6lpWcEA7zgVFICrdQSjSRVQH58NRlYeIpC00T5Jvw9wQ + +RUN mkdir -p /app + +COPY requirements.txt /app + +WORKDIR /app + +RUN pip3 install -r requirements.txt + +COPY . /app + +CMD ["python", "app.py", "--host", "0.0.0.0"] + diff --git a/README.MD b/README.MD new file mode 100755 index 0000000..45375d0 --- /dev/null +++ b/README.MD @@ -0,0 +1,5 @@ + +# eamco + +eamco is a site where you can ask questions and put bounties on it. + diff --git a/app.py b/app.py new file mode 100755 index 0000000..89be1b0 --- /dev/null +++ b/app.py @@ -0,0 +1,13 @@ +# coding=utf-8 +from app import app + +PORT = 4052 +HOST = '0.0.0.0' + +if __name__ == '__main__': + app.run( + debug=True, + host=HOST, + port=PORT, + use_reloader=True + ) diff --git a/app/__init__.py b/app/__init__.py new file mode 100644 index 0000000..1fe974c --- /dev/null +++ b/app/__init__.py @@ -0,0 +1,186 @@ +# coding=utf-8 +from flask import Flask, jsonify +from flask_bcrypt import Bcrypt +from flask_cors import CORS +from flask_marshmallow import Marshmallow +import os +from flask_sqlalchemy import SQLAlchemy +from flask_session import Session +from flask_login import LoginManager +from sqlalchemy.orm import sessionmaker +from werkzeug.routing import BaseConverter + +import stripe + + +try: + from local_settings import ApplicationConfig +except Exception as e: + from settings import ApplicationConfig + + +app = Flask(__name__, + static_url_path='', + static_folder='static', + template_folder='templates') + + +app.config.from_object(ApplicationConfig) + +session = sessionmaker() + +check_enviroment = ApplicationConfig.CURRENT_SETTINGS +print(f"starting server with {check_enviroment} settings") + + +class RegexConverter(BaseConverter): + def __init__(self, url_map, *items): + super(RegexConverter, self).__init__(url_map) + self.regex = items[0] + + +app.url_map.converters['regex'] = RegexConverter +app.jinja_env.autoescape = True + + + +# configure stripe +# stripe_keys = { +# 'secret_key': os.environ['STRIPE_SECRET_KEY'], +# 'publishable_key': os.environ['STRIPE_PUBLISHABLE_KEY'], +# } + +stripe_keys = { + 'secret_key': 'sk_test_51OUbSMJznCGgUo9kWM2Uv0UjM0Ai6etCOOHVKkgFBVxO66VtIqlOFL6lpWcEA7zgVFICrdQSjSRVQH58NRlYeIpC00T5Jvw9wQ', + 'public_key': ' pk_test_51OUbSMJznCGgUo9krwqaJkCtdnROJ2gyTcUWQGOHcaREDqP8dPGhMmLTbI1sFiyiKiK3BOPasTayBnFFth0pb81g00qlPdABbC', +} +stripe.api_key = stripe_keys['secret_key'] + +app.config['SECRET_KEY'] = ApplicationConfig.SECRET_KEY +app.config['SESSION_TYPE'] = ApplicationConfig.SESSION_TYPE +app.config['SESSION_COOKIE_NAME'] = ApplicationConfig.SESSION_COOKIE_NAME +app.config['SESSION_COOKIE_SECURE'] = ApplicationConfig.SESSION_COOKIE_SECURE +app.config['SESSION_COOKIE_HTTPONLY'] = ApplicationConfig.SESSION_COOKIE_HTTPONLY +app.config['SESSION_COOKIE_SAMESITE'] = ApplicationConfig.SESSION_COOKIE_SAMESITE +app.config['SESSION_PERMANENT'] = ApplicationConfig.SESSION_PERMANENT +app.config['SESSION_USE_SIGNER'] = ApplicationConfig.SESSION_USE_SIGNER +app.config['SESSION_REDIS'] = ApplicationConfig.SESSION_REDIS + +session.configure(bind=ApplicationConfig.SQLALCHEMY_DATABASE_URI) +db = SQLAlchemy(app) +bcrypt = Bcrypt(app) +server_session = Session(app) +ma = Marshmallow(app) + + +login_manager = LoginManager(app) +login_manager.session_protection = 'strong' +login_manager.anonymous_user = "Guest" + + +@login_manager.request_loader +def load_user_from_request(request): + from app.classes.auth import Auth_User + # first, try to log in using the api_key url arg + api_key = request.args.get('api_key') + if api_key: + user = db.session\ + .query(Auth_User)\ + .filter_by(api_key=api_key)\ + .first() + if user: + return user + # next, try to log in using Basic Auth + api_key_auth = request.headers.get('Authorization') + if api_key_auth: + api_key = api_key_auth.replace('bearer ', '', 1) + if api_key.startswith('"') and api_key.endswith('"'): + api_key = api_key[1:-1] + user = db.session\ + .query(Auth_User)\ + .filter_by(api_key=api_key)\ + .first() + if user: + return user + return None + + +api_main = { + "origins": [ApplicationConfig.ORIGIN_URL], + "methods": ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD"], + "allow_headers": ['Authorization', 'application/json', 'authorization', 'Content-Type', + 'Access-Control-Allow-Headers', 'Origin,Accept', + 'X-Requested-With', 'Content-Type', 'Access-Control-Request-Method', + 'Access-Control-Request-Headers'] +} +cors = CORS(app, supports_credentials=True, resources={r'/*': api_main}) + + +# bind a function after each request, even if an exception is encountered. +@app.teardown_request +def teardown_request(error): + db.session.remove() + + +@app.teardown_appcontext +def teardown_appcontext(error): + db.session.remove() + + +@app.errorhandler(500) +def internal_error500(): + return jsonify({"error": "Internal Error 500"}), 500 + + +@app.errorhandler(502) +def internal_error502(): + return jsonify({"error": "Internal Error 502"}), 502 + + +@app.errorhandler(404) +def internal_error404(): + return jsonify({"error": "Internal Error 400"}), 400 + + +@app.errorhandler(401) +def internal_error404(): + return jsonify({"error": "Internal Error 401"}), 401 + + +@app.errorhandler(400) +def internal_error400(): + return jsonify({"error": "Internal Error 400"}), 400 + + +@app.errorhandler(413) +def to_large_file(): + return jsonify({"error": "File is too large. Use a smaller image/file."}), 413 + + +@app.errorhandler(403) +def internal_error403(): + return jsonify({"error": "Internal Error 403"}), 403 + + +@app.errorhandler(405) +def internal_error(): + return jsonify({"error": "Internal Error 405"}), 405 + +# link locations +from .main import main as main_blueprint +app.register_blueprint(main_blueprint, url_prefix='/main') + +from .pay import pay as pay_blueprint +app.register_blueprint(pay_blueprint, url_prefix='/pay') + +from .update import update as update_blueprint +app.register_blueprint(update_blueprint, url_prefix='/update') + +from .auth import auth as auth_blueprint +app.register_blueprint(auth_blueprint, url_prefix='/auth') + + +with app.app_context(): + db.configure_mappers() + db.create_all() + db.session.commit() diff --git a/app/auth/__init__.py b/app/auth/__init__.py new file mode 100644 index 0000000..78d0fec --- /dev/null +++ b/app/auth/__init__.py @@ -0,0 +1,8 @@ +# coding=utf-8 +from flask import Blueprint + + +auth = Blueprint('auth', __name__) + + +from . import views \ No newline at end of file diff --git a/app/auth/views.py b/app/auth/views.py new file mode 100644 index 0000000..403ef19 --- /dev/null +++ b/app/auth/views.py @@ -0,0 +1,42 @@ +from flask import request, jsonify +from app.auth import auth +from app import db +from app.classes.auth import Auth_User + + +@auth.route("/whoami", methods=["GET"]) +def check_session(): + """ + Checks auth token to ensure user is authenticated + """ + + api_key = request.headers.get('Authorization') + if not api_key: + return jsonify({"error": "True"}), 200 + else: + api_key = api_key.replace('bearer ', '', 1) + api_key = api_key.replace('"', '') + user_exists = db.session\ + .query(Auth_User)\ + .filter(Auth_User.api_key == api_key)\ + .first() + if not user_exists: + return jsonify({"error": True}), 200 + else: + user = db.session\ + .query(Auth_User)\ + .filter(Auth_User.api_key == api_key)\ + .first() + + return jsonify({ + "ok": True, + 'user': { + 'user_name': user.display_name, + 'user_email': user.email, + 'user_admin': user.admin_role, + 'token': user.api_key, + 'confirmed': user.confirmed + }, + 'token': user.api_key + }), 200 + diff --git a/app/classes/__init__.py b/app/classes/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/classes/auth.py b/app/classes/auth.py new file mode 100644 index 0000000..756cf02 --- /dev/null +++ b/app/classes/auth.py @@ -0,0 +1,79 @@ +from flask_login import UserMixin, AnonymousUserMixin +from app import db, ma, login_manager +from datetime import datetime +from uuid import uuid4 + + +def get_uuid(): + return uuid4().hex + + +class Auth_User(UserMixin, db.Model): + __tablename__ = 'auth_users' + __bind_key__ = 'eamco' + __table_args__ = {"schema": "public"} + + id = db.Column(db.Integer, + autoincrement=True, + primary_key=True, + unique=True) + uuid = db.Column(db.String(32), default=get_uuid) + api_key = db.Column(db.TEXT) + username = db.Column(db.VARCHAR(40)) + password_hash = db.Column(db.TEXT) + member_since = db.Column(db.TIMESTAMP(), default=datetime.utcnow()) + email = db.Column(db.VARCHAR(350)) + last_seen = db.Column(db.TIMESTAMP(), default=datetime.utcnow()) + admin = db.Column(db.INTEGER) + admin_role = db.Column(db.INTEGER) + confirmed = db.Column(db.INTEGER) + + def __init__(self, + username, + api_key, + password_hash, + member_since, + email, + last_seen, + admin, + admin_role, + confirmed, + ): + self.username = username + self.api_key = api_key + self.password_hash = password_hash + self.member_since = member_since + self.email = email + self.last_seen = last_seen + self.admin = admin + self.admin_role = admin_role + self.confirmed = confirmed + + def is_authenticated(self): + return True + + def is_active(self): + return True + + def is_anonymous(self): + return False + + def get_id(self): + return self.id + + +class Auth_User_Schema(ma.SQLAlchemyAutoSchema): + class Meta: + model = Auth_User + + +user_schema = Auth_User_Schema() +users_schema = Auth_User_Schema(many=True) + + +class AnonymousUser(AnonymousUserMixin): + def __init__(self): + self.username = 'Guest' + + +login_manager.anonymous_user = AnonymousUser \ No newline at end of file diff --git a/app/classes/company.py b/app/classes/company.py new file mode 100644 index 0000000..362bac5 --- /dev/null +++ b/app/classes/company.py @@ -0,0 +1,25 @@ +from app import db, ma + + + +class Company_Company(db.Model): + __tablename__ = 'company_company' + __bind_key__ = 'eamco' + __table_args__ = {"schema": "public"} + + id = db.Column(db.Integer, + primary_key=True, + autoincrement=True, + unique=False) + + company_dba_name = db.Column(db.VARCHAR(250)) + company_llc_name = db.Column(db.VARCHAR(250)) + company_town = db.Column(db.VARCHAR(140)) + company_state = db.Column(db.VARCHAR(140)) + company_zip = db.Column(db.INTEGER) + + +class Company_Company_schema(ma.SQLAlchemyAutoSchema): + class Meta: + model = Company_Company + diff --git a/app/classes/customer.py b/app/classes/customer.py new file mode 100644 index 0000000..7e046d3 --- /dev/null +++ b/app/classes/customer.py @@ -0,0 +1,81 @@ + +from app import db, ma, login_manager +from datetime import datetime + + +class Customer_Customer(db.Model): + __tablename__ = 'customer_customer' + __bind_key__ = 'eamco' + __table_args__ = {"schema": "public"} + + id = db.Column(db.Integer, + primary_key=True, + autoincrement=True, + unique=False) + customer_last_name = db.Column(db.VARCHAR(250)) + customer_first_name = db.Column(db.VARCHAR(250)) + customer_town = db.Column(db.VARCHAR(140)) + customer_state = db.Column(db.INTEGER) + customer_zip = db.Column(db.VARCHAR(25)) + customer_first_call = db.Column(db.TIMESTAMP(), default=datetime.utcnow()) + customer_email = db.Column(db.VARCHAR(500)) + customer_automatic = db.Column(db.INTEGER) + customer_phone_number = db.Column(db.VARCHAR(25)) + customer_home_type = db.Column(db.INTEGER) + customer_apt = db.Column(db.VARCHAR(140)) + customer_address = db.Column(db.VARCHAR(1000)) + + +class Customer_Customer_schema(ma.SQLAlchemyAutoSchema): + class Meta: + model = Customer_Customer + + +class Customer_Property(db.Model): + __tablename__ = 'customer_property' + __bind_key__ = 'eamco' + __table_args__ = {"schema": "public"} + + id = db.Column(db.Integer, + primary_key=True, + autoincrement=True, + unique=False) + + customer_id = db.Column(db.INTEGER) + + # residential = 0 + # condo = 1 + # apartment = 2 + # commercial = 3 + # business = 4 + # vehicle = 4 + customer_property_type = db.Column(db.INTEGER) + + +class Customer_Property_schema(ma.SQLAlchemyAutoSchema): + class Meta: + model = Customer_Property + + +class Customer_Payment_Credit(db.Model): + __tablename__ = 'customer_payment' + __bind_key__ = 'eamco' + __table_args__ = {"schema": "public"} + + id = db.Column(db.Integer, + primary_key=True, + autoincrement=True, + unique=False) + + customer_id = db.Column(db.INTEGER) + + credit_card_type = db.Column(db.INTEGER) + credit_card_name = db.Column(db.VARCHAR(240)) + credit_card_number = db.Column(db.VARCHAR(140)) + credit_card_security = db.Column(db.VARCHAR(140)) + customer_card_expiration = db.Column(db.TIMESTAMP(), default=datetime.utcnow()) + + +class Customer_Payment_Credit_schema(ma.SQLAlchemyAutoSchema): + class Meta: + model = Customer_Payment_Credit \ No newline at end of file diff --git a/app/classes/delivery.py b/app/classes/delivery.py new file mode 100644 index 0000000..3c07179 --- /dev/null +++ b/app/classes/delivery.py @@ -0,0 +1,113 @@ +from app import db, ma, login_manager +from datetime import datetime + + +class Delivery_Delivery(db.Model): + __tablename__ = 'delivery_delivery' + __bind_key__ = 'eamco' + __table_args__ = {"schema": "public"} + + id = db.Column(db.Integer, + primary_key=True, + autoincrement=True, + unique=False) + + customer_id = db.Column(db.INTEGER) + customer_name = db.Column(db.VARCHAR(1000)) + customer_address = db.Column(db.VARCHAR(1000)) + customer_town = db.Column(db.VARCHAR(140)) + customer_state = db.Column(db.VARCHAR(140)) + customer_zip = db.Column(db.INTEGER) + # how many gallons ordered + gallons_ordered = db.Column(db.INTEGER) + # if customer asked for a fill + customer_asked_for_fill = db.Column(db.INTEGER) + # integer value if delivered, waiting, cancelled etc + gallons_delivered = db.Column(db.INTEGER) + # if customer has a full tank + customer_filled = db.Column(db.INTEGER) + + # integer value if delivered, waiting, cancelled etc + # waiting = 0 + # delivered = 1 + # out for delivery = 2 + # cancelled = 3 + # partial delivery = 4 + delivery_status = db.Column(db.INTEGER) + + # when the call to order took place + when_ordered = db.Column(db.TIMESTAMP(), default=datetime.utcnow()) + # when the delivery date happened + when_delivered = db.Column(db.TIMESTAMP(), default=None) + # when the delivery is expected ie what day + expected_delivery_date = db.Column(db.DATE(), default=None) + # automatic delivery + automatic = db.Column(db.INTEGER) + + # OIL info and id from table + oil_id = db.Column(db.INTEGER) + supplier_price = db.Column(db.DECIMAL(50, 2)) + customer_price = db.Column(db.DECIMAL(50, 2)) + + # weather + customer_temperature = db.Column(db.DECIMAL(50, 2)) + + # services + dispatcher_notes = db.Column(db.TEXT()) + prime = db.Column(db.INTEGER) + same_day = db.Column(db.INTEGER) + + # cash = 0 + # credit = 0 + payment_type = db.Column(db.INTEGER) + + +class Delivery_Delivery_schema(ma.SQLAlchemyAutoSchema): + class Meta: + model = Delivery_Delivery + + +class Delivery_Payment(db.Model): + __tablename__ = 'delivery_payment' + __bind_key__ = 'eamco' + __table_args__ = {"schema": "public"} + + id = db.Column(db.Integer, + primary_key=True, + autoincrement=True, + unique=False) + + delivery_id = db.Column(db.INTEGER) + time_added = db.Column(db.TIMESTAMP(), default=datetime.utcnow()) + total_amount_oil = db.Column(db.DECIMAL(50, 2)) + total_amount_emergency = db.Column(db.DECIMAL(50, 2)) + total_amount_prime = db.Column(db.DECIMAL(50, 2)) + total_amount_fee = db.Column(db.DECIMAL(50, 2)) + total_amount = db.Column(db.DECIMAL(50, 2)) + + +class Delivery_Payment_schema(ma.SQLAlchemyAutoSchema): + class Meta: + model = Delivery_Payment + + +class Delivery_Notes_Driver(db.Model): + __tablename__ = 'delivery_notes' + __bind_key__ = 'eamco' + __table_args__ = {"schema": "public"} + + id = db.Column(db.Integer, + primary_key=True, + autoincrement=True, + unique=False) + + delivery_id = db.Column(db.INTEGER) + driver_comments = db.Column(db.TEXT) + time_added = db.Column(db.TIMESTAMP(), default=datetime.utcnow()) + driver_id = db.Column(db.INTEGER) + driver_name = db.Column(db.VARCHAR(140)) + + +class Delivery_Notes_Driver_schema(ma.SQLAlchemyAutoSchema): + class Meta: + model = Delivery_Notes_Driver diff --git a/app/classes/employee.py b/app/classes/employee.py new file mode 100644 index 0000000..9498c2e --- /dev/null +++ b/app/classes/employee.py @@ -0,0 +1,73 @@ +from app import db, ma +from datetime import datetime + + +class Employee_Employee(db.Model): + __tablename__ = 'employee_employee' + __bind_key__ = 'eamco' + __table_args__ = {"schema": "public"} + + id = db.Column(db.Integer, + primary_key=True, + autoincrement=True, + unique=False) + + user_id = db.Column(db.INTEGER) + employee_first_name = db.Column(db.VARCHAR(250)) + employee_last_name = db.Column(db.VARCHAR(250)) + employee_apt = db.Column(db.VARCHAR(250)) + employee_address = db.Column(db.VARCHAR(1000)) + employee_town = db.Column(db.VARCHAR(140)) + employee_state = db.Column(db.VARCHAR(140)) + employee_zip = db.Column(db.VARCHAR(25)) + employee_birthday = db.Column(db.DATE(), default=datetime.utcnow()) + employee_type = db.Column(db.INTEGER) + employee_phone_number = db.Column(db.VARCHAR(25)) + employee_start_date = db.Column(db.DATE(), default=datetime.utcnow()) + employee_end_date = db.Column(db.DATE(), default=None) + + +class Employee_Employee_schema(ma.SQLAlchemyAutoSchema): + class Meta: + model = Employee_Employee + + +class Employee_Credentials(db.Model): + __tablename__ = 'employee_credentials' + __bind_key__ = 'eamco' + __table_args__ = {"schema": "public"} + + id = db.Column(db.Integer, + primary_key=True, + autoincrement=True, + unique=False) + employee_id = db.Column(db.INTEGER) + employee_name = db.Column(db.VARCHAR(140)) + employee_cdl_expire = db.Column(db.TIMESTAMP(), default=datetime.utcnow()) + employee_hvac_expire = db.Column(db.TIMESTAMP(), default=datetime.utcnow()) + + +class Employee_Credentials_schema(ma.SQLAlchemyAutoSchema): + class Meta: + model = Employee_Credentials + + +class Employee_Vacation(db.Model): + __tablename__ = 'employee_vacation' + __bind_key__ = 'eamco' + __table_args__ = {"schema": "public"} + + id = db.Column(db.Integer, + primary_key=True, + autoincrement=True, + unique=False) + employee_id = db.Column(db.INTEGER) + employee_name = db.Column(db.VARCHAR(140)) + employee_total_days_off = db.Column(db.INTEGER) + employee_days_off_multiplier = db.Column(db.DECIMAL(50, 2)) + employee_days_off_per_year = db.Column(db.INTEGER) + + +class Employee_Vacation_schema(ma.SQLAlchemyAutoSchema): + class Meta: + model = Employee_Vacation diff --git a/app/classes/oil.py b/app/classes/oil.py new file mode 100644 index 0000000..af9c1f9 --- /dev/null +++ b/app/classes/oil.py @@ -0,0 +1,25 @@ + +from app import db, ma, login_manager +from datetime import datetime + + + +class Oil_Oil(db.Model): + __tablename__ = 'oil_oil' + __bind_key__ = 'eamco' + __table_args__ = {"schema": "public"} + + id = db.Column(db.Integer, + primary_key=True, + autoincrement=True, + unique=False) + + price_from_supplier = db.Column(db.DECIMAL(50, 2)) + price_for_customer = db.Column(db.DECIMAL(50, 2)) + price_for_employee = db.Column(db.DECIMAL(50, 2)) + date = db.Column(db.TIMESTAMP(), default=datetime.utcnow()) + +class Oil_Oil_schema(ma.SQLAlchemyAutoSchema): + class Meta: + model = Oil_Oil + diff --git a/app/classes/query.py b/app/classes/query.py new file mode 100644 index 0000000..f1d05ad --- /dev/null +++ b/app/classes/query.py @@ -0,0 +1,70 @@ + +from app import db, ma + + +class Query_EmployeeTypeList(db.Model): + __tablename__ = 'query_employee_type_list' + __bind_key__ = 'eamco' + __table_args__ = {"schema": "public"} + id = db.Column(db.Integer, primary_key=True, autoincrement=True) + value = db.Column(db.INTEGER) + text = db.Column(db.VARCHAR(140)) + + +class Query_EmployeeTypeList_Schema(ma.SQLAlchemyAutoSchema): + class Meta: + model = Query_EmployeeTypeList + id = ma.auto_field() + text = ma.auto_field() + value = ma.auto_field() + +class Query_StateList(db.Model): + __tablename__ = 'query_state_list' + __bind_key__ = 'eamco' + __table_args__ = {"schema": "public"} + id = db.Column(db.Integer, primary_key=True, autoincrement=True) + value = db.Column(db.INTEGER) + text = db.Column(db.VARCHAR(140)) + + +class Query_StateList_Schema(ma.SQLAlchemyAutoSchema): + class Meta: + model = Query_StateList + id = ma.auto_field() + text = ma.auto_field() + value = ma.auto_field() + + +class Query_CustomerTypeList(db.Model): + __tablename__ = 'query_customer_type_list' + __bind_key__ = 'eamco' + __table_args__ = {"schema": "public"} + id = db.Column(db.Integer, primary_key=True, autoincrement=True) + value = db.Column(db.INTEGER) + text = db.Column(db.VARCHAR(140)) + + +class Query_CustomerTypeList_Schema(ma.SQLAlchemyAutoSchema): + class Meta: + model = Query_CustomerTypeList + id = ma.auto_field() + text = ma.auto_field() + value = ma.auto_field() + + + +class Query_ServiceTypeList(db.Model): + __tablename__ = 'query_service_type_list' + __bind_key__ = 'eamco' + __table_args__ = {"schema": "public"} + id = db.Column(db.Integer, primary_key=True, autoincrement=True) + value = db.Column(db.INTEGER) + text = db.Column(db.VARCHAR(140)) + + +class Query_ServiceTypeList_Schema(ma.SQLAlchemyAutoSchema): + class Meta: + model = Query_ServiceTypeList + id = ma.auto_field() + text = ma.auto_field() + value = ma.auto_field() diff --git a/app/classes/service.py b/app/classes/service.py new file mode 100644 index 0000000..5b1ae50 --- /dev/null +++ b/app/classes/service.py @@ -0,0 +1,127 @@ + +from app import db, ma +from datetime import datetime + + + +class Service_Call(db.Model): + __tablename__ = 'service_call' + __bind_key__ = 'eamco' + __table_args__ = {"schema": "public"} + + id = db.Column(db.Integer, + primary_key=True, + autoincrement=True, + unique=False) + + customer_id = db.Column(db.INTEGER) + customer_last_name = db.Column(db.VARCHAR(250)) + customer_first_name = db.Column(db.VARCHAR(250)) + customer_town = db.Column(db.VARCHAR(140)) + customer_state = db.Column(db.INTEGER) + customer_zip = db.Column(db.VARCHAR(25)) + customer_apt = db.Column(db.VARCHAR(140)) + customer_address = db.Column(db.VARCHAR(1000)) + + #0 = closed + #1 = open + status = db.Column(db.INTEGER) + + # 0 = unknown + # 1 = cleaning / tuneup + # 2 = problem + # 3 = install + # 3 = callback + service_type = db.Column(db.INTEGER) + + # when the call to service took place + when_called = db.Column(db.DATE(), default=datetime.utcnow()) + # what day the call will take place + scheduled_date = db.Column(db.DATE(), default=datetime.utcnow()) + # what day the call will take place + scheduled_time = db.Column(db.INTEGER) + # when the service took place + when_serviced = db.Column(db.DATE(), default=datetime.utcnow()) + + # is the call finished or not + # 0 = open + #1 = finished + completed = db.Column(db.INTEGER) + tech_id = db.Column(db.INTEGER) + tech_first_name = db.Column(db.VARCHAR(300)) + tech_last_name = db.Column(db.VARCHAR(300)) + + + +class Service_Call_schema(ma.SQLAlchemyAutoSchema): + class Meta: + model = Service_Call + + +class Service_Call_Money(db.Model): + __tablename__ = 'service_money' + __bind_key__ = 'eamco' + __table_args__ = {"schema": "public"} + + id = db.Column(db.Integer, + primary_key=True, + autoincrement=True, + unique=False) + + service_call_id = db.Column(db.INTEGER) + hours = db.Column(db.DECIMAL(50, 2)) + cost_per_hour = db.Column(db.DECIMAL(50, 2)) + parts_cost = db.Column(db.DECIMAL(50, 2)) + total_cost_service = db.Column(db.DECIMAL(50, 2)) + +class Service_Call_Money_schema(ma.SQLAlchemyAutoSchema): + class Meta: + model = Service_Call_Money + + +class Service_Call_Notes_Dispatcher(db.Model): + __tablename__ = 'service_notes_dispatcher' + __bind_key__ = 'eamco' + __table_args__ = {"schema": "public"} + + id = db.Column(db.Integer, + primary_key=True, + autoincrement=True, + unique=False) + + service_call_id = db.Column(db.INTEGER) + dispatcher_notes = db.Column(db.TEXT) + dispatcher_subject = db.Column(db.VARCHAR(1024)) + time_added = db.Column(db.TIMESTAMP(), default=datetime.utcnow()) + dispatcher_id = db.Column(db.INTEGER) + dispatcher_name = db.Column(db.VARCHAR(140)) + + +class Service_Call_Notes_Dispatcher_schema(ma.SQLAlchemyAutoSchema): + class Meta: + model = Service_Call_Notes_Dispatcher + + +class Service_Call_Notes_Technician(db.Model): + __tablename__ = 'service_notes_technician' + __bind_key__ = 'eamco' + __table_args__ = {"schema": "public"} + + id = db.Column(db.Integer, + primary_key=True, + autoincrement=True, + unique=False) + + service_call_id = db.Column(db.INTEGER) + technician_comments = db.Column(db.TEXT) + time_added = db.Column(db.TIMESTAMP(), default=datetime.utcnow()) + technician_id = db.Column(db.INTEGER) + technician_name = db.Column(db.VARCHAR(140)) + + +class Service_Call_Notes_Technician_schema(ma.SQLAlchemyAutoSchema): + class Meta: + model = Service_Call_Notes_Technician + + + diff --git a/app/classes/stats_customer.py b/app/classes/stats_customer.py new file mode 100644 index 0000000..3d7151e --- /dev/null +++ b/app/classes/stats_customer.py @@ -0,0 +1,29 @@ + +from app import db, ma, login_manager +from datetime import datetime + + +class Stats_Customer(db.Model): + __tablename__ = 'stats_customer' + __bind_key__ = 'eamco' + __table_args__ = {"schema": "public"} + + id = db.Column(db.Integer, + primary_key=True, + autoincrement=True, + unique=False) + + total_calls = db.Column(db.INTEGER) + service_calls_total = db.Column(db.INTEGER) + service_calls_total_spent = db.Column(db.DECIMAL(50, 2)) + service_calls_total_profit = db.Column(db.DECIMAL(50, 2)) + + + oil_deliveries = db.Column(db.INTEGER) + oil_total_gallons = db.Column(db.INTEGER) + oil_total_spent = db.Column(db.DECIMAL(50, 2)) + oil_total_profit = db.Column(db.DECIMAL(50, 2)) + +class Stats_Customer_schema(ma.SQLAlchemyAutoSchema): + class Meta: + model = Stats_Customer diff --git a/app/classes/stats_employee.py b/app/classes/stats_employee.py new file mode 100644 index 0000000..b6ee3cd --- /dev/null +++ b/app/classes/stats_employee.py @@ -0,0 +1,46 @@ + +from app import db, ma, login_manager +from datetime import datetime + + +class Stats_Employee_Oil(db.Model): + __tablename__ = 'stats_employee_oil' + __bind_key__ = 'eamco' + __table_args__ = {"schema": "public"} + + id = db.Column(db.Integer, + primary_key=True, + autoincrement=True, + unique=False) + + total_deliveries = db.Column(db.INTEGER) + total_gallons_delivered = db.Column(db.INTEGER) + total_primes = db.Column(db.INTEGER) + total_gallons_fuel = db.Column(db.INTEGER) + oil_total_profit_delivered = db.Column(db.DECIMAL(50, 2)) + +class Stats_Employee_Oil_schema(ma.SQLAlchemyAutoSchema): + class Meta: + model = Stats_Employee_Oil + + + + +class Stats_Employee_Service(db.Model): + __tablename__ = 'stats_employee_service' + __bind_key__ = 'eamco' + __table_args__ = {"schema": "public"} + + id = db.Column(db.Integer, + primary_key=True, + autoincrement=True, + unique=False) + + total_service_calls = db.Column(db.INTEGER) + total_service_calls_hours = db.Column(db.INTEGER) + total_gallons_fuel = db.Column(db.INTEGER) + total_amount_billed= db.Column(db.DECIMAL(50, 2)) + total_profit_made = db.Column(db.DECIMAL(50, 2)) +class Stats_Employee_Service_schema(ma.SQLAlchemyAutoSchema): + class Meta: + model = Stats_Employee_Service diff --git a/app/common/__init__.py b/app/common/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/common/decorators.py b/app/common/decorators.py new file mode 100644 index 0000000..603fd3e --- /dev/null +++ b/app/common/decorators.py @@ -0,0 +1,15 @@ +from flask_login import current_user +from flask import abort + +from functools import wraps + +def login_required(f): + @wraps(f) + def decorated_function(*args, **kwargs): + if current_user.is_authenticated: + pass + else: + abort(401) + return f(*args, **kwargs) + + return decorated_function diff --git a/app/create/__init__.py b/app/create/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/create/views.py b/app/create/views.py new file mode 100644 index 0000000..b5e80d5 --- /dev/null +++ b/app/create/views.py @@ -0,0 +1,41 @@ +from flask import jsonify +import stripe +from app.create import create + + +@create.route("/create/product", methods=["GET"]) +def create_product(): + """ + Creates a product + """ + create_a_product = stripe.Product.create( + name="Oil", + active=True, + description="One Gallon of Oil", + shippable=False, + ) + return jsonify({ + "ok": True, + 'info': create_a_product, + }), 200 + + + + +@create.route("/create/price", methods=["GET"]) +def create_product_price(): + """ + Sets the price of the product + """ + + create_price = stripe.Price.create( + product_data={"name": "Oil"}, + unit_amount=350, + currency="usd", + ) + + return jsonify({ + "ok": True, + 'info': create_price, + }), 200 + diff --git a/app/info/__init__.py b/app/info/__init__.py new file mode 100644 index 0000000..3e1f8c0 --- /dev/null +++ b/app/info/__init__.py @@ -0,0 +1,7 @@ +# coding=utf-8 + +from flask import Blueprint + +info = Blueprint('info', __name__) + +from . import views \ No newline at end of file diff --git a/app/info/views.py b/app/info/views.py new file mode 100644 index 0000000..449a766 --- /dev/null +++ b/app/info/views.py @@ -0,0 +1,17 @@ +from flask import request, jsonify +import stripe +from flask_login import current_user, logout_user, login_user, login_required +from app.info import info +from app import db + + + +@info.route("/balance", methods=["GET"]) +def get_balance(): + charge = stripe.Balance.retrieve() + return jsonify({ + "ok": True, + 'info': { + 'charge': charge, + }, + }), 200 \ No newline at end of file diff --git a/app/main/__init__.py b/app/main/__init__.py new file mode 100644 index 0000000..812deb1 --- /dev/null +++ b/app/main/__init__.py @@ -0,0 +1,7 @@ +# coding=utf-8 + +from flask import Blueprint + +main = Blueprint('main', __name__) + +from . import views \ No newline at end of file diff --git a/app/main/views.py b/app/main/views.py new file mode 100644 index 0000000..e80a92d --- /dev/null +++ b/app/main/views.py @@ -0,0 +1,31 @@ +from flask import jsonify, Response +from app import app +from app import stripe_keys + + +@app.route('/robots.txt') +@app.route('/sitemap.xml') +def static_from_root(): + def disallow(string): return 'Disallow: {0}'.format(string) + return Response("User-agent: *\n{0}\n".format("\n".join([ + disallow('/bin/*'), + disallow('/admin'), + ]))) + + +@app.route('/index', methods=['GET']) +@app.route('/', methods=['GET']) +def index(): + return jsonify({"success": "Api is online"}), 200 + + + +@app.route('/key') +def get_publishable_key(): + """ + Gets the key for the frontend + """ + stripe_config = {'publicKey': stripe_keys['public_key']} + + return jsonify(stripe_config) + diff --git a/app/pay/__init__.py b/app/pay/__init__.py new file mode 100644 index 0000000..d809050 --- /dev/null +++ b/app/pay/__init__.py @@ -0,0 +1,7 @@ +# coding=utf-8 + +from flask import Blueprint + +pay = Blueprint('pay', __name__) + +from . import views \ No newline at end of file diff --git a/app/pay/views.py b/app/pay/views.py new file mode 100644 index 0000000..f2dba6d --- /dev/null +++ b/app/pay/views.py @@ -0,0 +1,63 @@ +from flask import jsonify +import stripe +from app.pay import pay +from app import db +from app.classes.delivery import Delivery_Delivery +from app.classes.service import Service_Call_Money + + +@pay.route("/oil/", methods=["GET"]) +def create_charge_oil(delivery_id): + + get_oil_delivery = db.session\ + .query(Delivery_Delivery)\ + .filter(Delivery_Delivery.id == delivery_id)\ + .first() + + if get_oil_delivery.customer_asked_for_fill == 1: + gallons = get_oil_delivery.gallons_ordered + else: + gallons = 250 + + checkout_session = stripe.checkout.Session.create( + success_url='http://localhost:5173/success', + cancel_url='http://localhost:5173/canceled', + payment_method_types=['card'], + mode='payment', + line_items=[ + { + 'price': 'price_1OXTkZJznCGgUo9kIoz37lwg', + 'quantity': gallons, + }, + ] + + ) + return jsonify({'sessionId': checkout_session['id']}) + + +@pay.route("/service/", methods=["GET"]) +def create_charge_service(service_id): + + get_service_call = db.session\ + .query(Service_Call_Money)\ + .filter(Service_Call_Money.service_call_id == service_id)\ + .first() + + amount_to_charge = round(float(get_service_call.total_cost_service) * 100) + + ## TODO try stripe.charge.create ???! + checkout_session = stripe.checkout.Session.create( + success_url='http://localhost:5173/success', + cancel_url='http://localhost:5173/canceled', + payment_method_types=['card'], + mode='payment', + line_items=[ + { + 'price': 'price_1OXTkZJznCGgUo9kIoz37lwg', + 'quantity': amount_to_charge, + }, + + ] + + ) + return jsonify({'sessionId': checkout_session['id']}) \ No newline at end of file diff --git a/app/update/__init__.py b/app/update/__init__.py new file mode 100644 index 0000000..b9f746d --- /dev/null +++ b/app/update/__init__.py @@ -0,0 +1,7 @@ +# coding=utf-8 + +from flask import Blueprint + +update = Blueprint('update', __name__) + +from . import views \ No newline at end of file diff --git a/app/update/views.py b/app/update/views.py new file mode 100644 index 0000000..cd25795 --- /dev/null +++ b/app/update/views.py @@ -0,0 +1,73 @@ +from flask import jsonify +import stripe +from app.update import update + + + +@update.route('/oil') +def update_price_oil(): + + update_price_oil = stripe.Price.modify( + "price_1MoBy5LkdIwHu7ixZhnattbh", + unit_amount=350, + ) + + return jsonify({- + "ok": True, + 'info': update_price_oil + }), 200 + + +@update.route('/prime') +def update_price_prime(): + + update_price_prime = stripe.Price.modify( + "price_1MoBy5LkdIwHu7ixZhnattbh", + unit_amount=350, + ) + + return jsonify({- + "ok": True, + 'info': update_price_prime + }), 200 + + +@update.route('/emergency') +def update_price_emergency(): + + update_price_emergency = stripe.Price.modify( + "price_1MoBy5LkdIwHu7ixZhnattbh", + unit_amount=350, + ) + + return jsonify({- + "ok": True, + 'info': update_price_emergency + }), 200 + + +@update.route('/sameday') +def update_price_sameday(): + + update_price_sameday = stripe.Price.modify( + "price_1MoBy5LkdIwHu7ixZhnattbh", + unit_amount=350, + ) + + return jsonify({- + "ok": True, + 'info': update_price_sameday + }), 200 + + +@update.route('/service') +def update_price_service(): + + update_price_service = stripe.Price.modify( + "price_1MoBy5LkdIwHu7ixZhnattbh", + unit_amount=17500, + ) + return jsonify({- + "ok": True, + 'info': update_price_service + }), 200 \ No newline at end of file diff --git a/local_settings.py b/local_settings.py new file mode 100755 index 0000000..3a7fed2 --- /dev/null +++ b/local_settings.py @@ -0,0 +1,64 @@ +import redis + + +class ApplicationConfig: + """ + Basic Configuration for a generic User + """ + + # configuration + STRIPE_PUBLIC_KEY = 'pk_test_51OUbSMJznCGgUo9krwqaJkCtdnROJ2gyTcUWQGOHcaREDqP8dPGhMmLTbI1sFiyiKiK3BOPasTayBnFFth0pb81g00qlPdABbC' + STRIPE_SECRET_KEY = 'sk_test_51OUbSMJznCGgUo9kWM2Uv0UjM0Ai6etCOOHVKkgFBVxO66VtIqlOFL6lpWcEA7zgVFICrdQSjSRVQH58NRlYeIpC00T5Jvw9wQ' + + + + CURRENT_SETTINGS = 'LOCAL' + # databases info + POSTGRES_USERNAME = 'postgres' + POSTGRES_PW = 'postgres' + POSTGRES_SERVER = '172.19.0.1:5432' + POSTGRES_DBNAME00 = 'eamco' + SQLALCHEMY_DATABASE_URI = "postgresql+psycopg2://{}:{}@{}/{}".format(POSTGRES_USERNAME, + POSTGRES_PW, + POSTGRES_SERVER, + POSTGRES_DBNAME00 + ) + SQLALCHEMY_BINDS = {'eamco': SQLALCHEMY_DATABASE_URI} + # sqlalchemy config + SQLALCHEMY_TRACK_MODIFICATIONS = False + TRAP_HTTP_EXCEPTIONS = True + PROPAGATE_EXCEPTIONS = True + DEBUG = True + UPLOADED_FILES_DEST_ITEM = '/data/item' + + # file uploads + UPLOADED_FILES_ALLOW = ['png', 'jpeg', 'jpg', 'png', 'gif'] + MAX_CONTENT_LENGTH = 5 * 2500 * 2500 + ALLOWED_EXTENSIONS = ['png', 'jpeg', 'jpg', 'png', 'gif'] + + # secret keys + SECRET_KEY = "youwillneverguessthiskeycia" + + # sessions + SESSION_COOKIE_NAME = "quest_session" + SESSION_COOKIE_SECURE = False + SESSION_COOKIE_HTTPONLY = True + REMEMBER_COOKIE_HTTPONLY = True + SESSION_COOKIE_SAMESITE = "Strict" + SESSION_PERMANENT = False + SESSION_USE_SIGNER = True + + # redis config + SESSION_TYPE = "redis" + SESSION_REDIS = redis.from_url("redis://redis:6379") + + # CORS + ORIGIN_URL = "http://localhost:5173" + CORS_SEND_WILDCARD = False + CORS_SUPPORT_CREDENTIALS = True + CORS_EXPOSE_HEADERS = None + CORS_ALLOW_HEADERS = "*" + CORS_ORIGIN_WHITELIST = ['http://localhost', '*'] + + + diff --git a/requirements.txt b/requirements.txt new file mode 100755 index 0000000..d470684 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,22 @@ +flask==2.3.3 +flask_sqlalchemy +flask_session==0.5.0 +flask-login==0.6.3 +flask-moment +flask-paranoid +flask-bcrypt +flask-cors +flask_marshmallow +gunicorn +python-dateutil +python-dotenv +marshmallow-sqlalchemy +psycopg2-binary +redis +sqlalchemy +flask_wtf +flask_mail +Werkzeug==2.3.8 + + +stripe \ No newline at end of file diff --git a/runProduction.py b/runProduction.py new file mode 100755 index 0000000..15c64c4 --- /dev/null +++ b/runProduction.py @@ -0,0 +1,11 @@ +# coding=utf-8 +from app import app + +PORT = 6000 +HOST = '0.0.0.0' +if __name__ == '__main__': + app.run( + host=HOST, + port=PORT, + threaded=True + )