diff --git a/Dockerfile b/Dockerfile index 9072cc7..4ef6e61 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,15 +1,20 @@ -FROM python:3.10-slim +FROM python@sha256:38000b248a186dcae150fe2f64d23bd44a0730347d1e5e4d1faedd449a9a4913 RUN useradd scan2kasse WORKDIR /home/scan2kasse +RUN apt update && apt upgrade +RUN apt install -y libpq-dev gcc g++ + COPY requirements.txt requirements.txt RUN python -m venv venv RUN venv/bin/pip install -r requirements.txt +RUN venv/bin/pip install gunicorn COPY app app COPY migrations migrations +COPY configs configs COPY run.py boot.sh ./ RUN chmod +x boot.sh diff --git a/app/__init__.py b/app/__init__.py index 86a4bee..3419af4 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -1,3 +1,4 @@ +from configs.config import Config from flask import Flask from flask_bootstrap import Bootstrap from flask_login import LoginManager @@ -7,7 +8,6 @@ from logging import getLogger from logging.config import fileConfig from os import makedirs from os.path import dirname, exists -from yaml import safe_load try: dir_name = dirname(__file__) @@ -18,10 +18,10 @@ try: except NameError: DIR = "./" -if not exists(DIR + "logs"): - makedirs(DIR + "logs") +if not exists(DIR + "../logs"): + makedirs(DIR + "../logs") -fileConfig(DIR + "configs/log.conf") +fileConfig(DIR + "../configs/log.conf") LOGGER = getLogger("root") bootstrap = Bootstrap() @@ -31,9 +31,9 @@ login.login_view = 'web_login' migrate = Migrate() -def create_app(config_file="configs/config.yaml"): +def create_app(config_class=Config): app = Flask(__name__) - app.config.from_file(config_file, safe_load) + app.config.from_object(config_class) bootstrap.init_app(app) db.init_app(app) login.init_app(app) diff --git a/app/auth/routes.py b/app/auth/routes.py index 33c3f58..f24eab8 100644 --- a/app/auth/routes.py +++ b/app/auth/routes.py @@ -13,7 +13,7 @@ def web_register(): return redirect(url_for('main.index')) form = RegistrationForm() if form.validate_on_submit(): - user = User(username=form.username.data, email=form.email.data) + user = User(email=form.email.data) user.set_password(form.password.data) db.session.add(user) db.session.commit() @@ -27,9 +27,9 @@ def web_login(): return redirect(url_for('main.index')) form = LoginForm() if form.validate_on_submit(): - user = User.query.filter_by(username=form.username.data).first() + user = User.query.filter_by(email=form.email.data).first() if user is None or not user.check_password(form.password.data): - flash('Invalid username or password') + flash('Invalid email or password') return redirect(url_for('auth.web_login')) login_user(user, remember=form.remember_me.data) next_page = request.args.get('next') diff --git a/app/configs/config.yaml.template b/app/configs/config.yaml.template deleted file mode 100644 index 670149d..0000000 --- a/app/configs/config.yaml.template +++ /dev/null @@ -1,3 +0,0 @@ -SECRET_KEY: "MY_5€cr37_K€Y" -SQLALCHEMY_DATABASE_URI: "dialect+driver://username:password@host:port/database" -SQLALCHEMY_TRACK_MODIFICATIONS: False \ No newline at end of file diff --git a/app/main/routes.py b/app/main/routes.py index 94c5bbb..be4a70d 100644 --- a/app/main/routes.py +++ b/app/main/routes.py @@ -44,9 +44,9 @@ def get_report_from_user(): return jsonify(result_list) else: if "establishment" in request.args: - return render_template("overview.html", results=result_list, establishment = Establishment.query.get(int(request.args['establishment']))) + return render_template("main/overview.html", results=result_list, establishment = Establishment.query.get(int(request.args['establishment']))) else: - return render_template("overview.html", results=result_list) + return render_template("main/overview.html", results=result_list) @bp.route('/token_authorization') def token_authorization(): @@ -95,7 +95,7 @@ def new_item(): db.session.add(new_item) db.session.commit() return redirect(url_for('index')) - return render_template('admin/new_item.html', form=form) + return render_template('main/new_item.html', form=form) @bp.route('/overview/register_boughts', methods=['GET']) @login_required @@ -113,4 +113,4 @@ def check_unregistered_items(): if request.content_type == "application/json": return jsonify(result_list) else: - return render_template("overview.html", results=result_list) \ No newline at end of file + return render_template("main/overview.html", results=result_list) \ No newline at end of file diff --git a/app/models.py b/app/models.py index 484e8de..e4e1f03 100644 --- a/app/models.py +++ b/app/models.py @@ -24,7 +24,7 @@ class User(UserMixin, db.Model): return check_password_hash(self.password_hash, password) def __repr__(self) -> str: - return f"" + return f"" class Establishment(db.Model): id = db.Column(db.BigInteger, primary_key=True) @@ -64,7 +64,7 @@ class Category(db.Model): return f"" class Item(db.Model): - id = db.Column(db.BigInteger, primary_key=True) + id = db.Column(db.BigInteger, primary_key=True, autoincrement=False) name = db.Column(db.String(64), nullable=False) brand = db.Column(db.ForeignKey('brand.id'), nullable=False, server_onupdate=db.FetchedValue()) description = db.Column(db.Text, nullable=False) @@ -104,7 +104,7 @@ class AmountChange(db.Model): return f"" class Receipt(db.Model): - id = db.Column(db.Numeric(precision=22, scale=0), primary_key=True) + id = db.Column(db.Numeric(precision=24, scale=0), primary_key=True, autoincrement=False) date = db.Column(db.Date, nullable=False) from_user = db.Column(db.ForeignKey("login_token.token"), server_onupdate=db.FetchedValue()) registered = db.Column(db.Boolean, nullable=False, server_default=str(False)) diff --git a/app/templates/main/overview.html b/app/templates/main/overview.html index 2d9e8c1..588123e 100644 --- a/app/templates/main/overview.html +++ b/app/templates/main/overview.html @@ -3,7 +3,7 @@ {% block app_content %} {% if establishment %} {% if current_user.id == establishment.owner %} - {% endif %} @@ -12,7 +12,7 @@
diff --git a/app/utils/database_utils.py b/app/utils/database_utils.py index 892c3bc..37f3ad2 100644 --- a/app/utils/database_utils.py +++ b/app/utils/database_utils.py @@ -29,7 +29,7 @@ def insert_bought_items(token: str, items: dict, date: str = None): return {'user':token, 'date': date, 'items': items} if items else {} def get_report(**kwargs): - query_select = db.session.query(bwp.c.token, User.username, bwp.c.date, bwp.c.item, Item.name, bwp.c.amount, bwp.c.price) + query_select = db.session.query(bwp.c.token, User.email, bwp.c.date, bwp.c.item, Item.name, bwp.c.amount, bwp.c.price) query_select = query_select.select_from(bwp).join(LoginToken, LoginToken.token==bwp.c.token).join(User, LoginToken.user==User.id).join(Item, Item.id==bwp.c.item) match kwargs: case {"token": token}: @@ -62,7 +62,7 @@ def get_unregistered_and_register(intEstablishment: int): if current_user.id != establishment.owner: LOGGER.debug("!!!Wrong User!!!") return False - query_select = db.session.query(bwp.c.token, User.username, bwp.c.date, bwp.c.item, Item.name, bwp.c.amount, bwp.c.price) + query_select = db.session.query(bwp.c.token, User.email, bwp.c.date, bwp.c.item, Item.name, bwp.c.amount, bwp.c.price) query_select = query_select.select_from(bwp).join(LoginToken, LoginToken.token==bwp.c.token).join(User, LoginToken.user==User.id) query_select = query_select.join(Item, Item.id==bwp.c.item).join(Bought, and_(Bought.token==bwp.c.token, Bought.item==bwp.c.item, Bought.date==bwp.c.date)) query_select = query_select.filter(bwp.c.token.in_(db.session.query(LoginToken.token).filter_by(establishment = intEstablishment))) diff --git a/app/utils/view_utils.py b/app/utils/view_utils.py index 85191e3..07f003c 100644 --- a/app/utils/view_utils.py +++ b/app/utils/view_utils.py @@ -9,7 +9,7 @@ def group_results(results: tuple) -> list: try: result_user_index = [result[0] == result_item['id'] for result_item in result_list].index(True) except ValueError as e: - result_list.append({"id": result[0], "username": result[1], "sum": 0, "item_infos": []}) + result_list.append({"id": result[0], "email": result[1], "sum": 0, "item_infos": []}) result_user_index = -1 result_user = result_list[result_user_index] try: diff --git a/boot.sh b/boot.sh index 2f7ea1d..12b7ce9 100644 --- a/boot.sh +++ b/boot.sh @@ -1,5 +1,13 @@ #!/bin/bash source venv/bin/activate +while true; do + flask db upgrade 05fce74b56cb + if [[ "$?" == "0" ]]; then + break + fi + echo Deploy command failed, retrying in 5 secs... + sleep 5 +done +flask db upgrade 2be4d1ae5493 flask db upgrade -flask translate compile -python run.py \ No newline at end of file +exec gunicorn -b :5000 --access-logfile - --error-logfile - run:app \ No newline at end of file diff --git a/configs/config.py b/configs/config.py new file mode 100644 index 0000000..958b259 --- /dev/null +++ b/configs/config.py @@ -0,0 +1,15 @@ +import os +from dotenv import load_dotenv + +basedir = os.path.abspath(os.path.dirname(__file__)) +load_dotenv(os.path.join(basedir, '.env')) + + +class Config(object): + SECRET_KEY = os.environ.get('SECRET_KEY') or "MY_5€cr37_K€Y" + SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL', '').replace( + 'postgres://', 'postgresql://') or \ + (f"postgresql://{os.environ.get('DATABASE_USER', 'scan2kasse')}:{os.environ.get('DATABASE_PASS', 'asdf1337')}" + f"@{os.environ.get('DATABASE_HOST', 'localhost')}:{os.environ.get('DATABASE_PORT', '5432')}" + f"/{os.environ.get('DATABASE_DB', '') or os.environ.get('DATABASE_USER', 'scan2kasse')}") + SQLALCHEMY_TRACK_MODIFICATIONS = False diff --git a/app/configs/log.conf b/configs/log.conf similarity index 90% rename from app/configs/log.conf rename to configs/log.conf index 7df15ac..9843f82 100644 --- a/app/configs/log.conf +++ b/configs/log.conf @@ -25,7 +25,7 @@ formatter = stdout class = logging.FileHandler level = INFO formatter = stdout -kwargs = {"filename": "app/logs/infos.log"} +kwargs = {"filename": "logs/infos.log"} [formatter_stdout] format = %(asctime)s [%(threadName)s] [%(levelname)s]: %(message)s \ No newline at end of file diff --git a/migrations/versions/05fce74b56cb_full_structure.py b/migrations/versions/05fce74b56cb_full_structure.py index b570d82..898e1df 100644 --- a/migrations/versions/05fce74b56cb_full_structure.py +++ b/migrations/versions/05fce74b56cb_full_structure.py @@ -54,7 +54,7 @@ def upgrade(): sa.UniqueConstraint('token') ) op.create_table('receipt', - sa.Column('id', sa.Numeric(precision=22, scale=0), nullable=False), + sa.Column('id', sa.Numeric(precision=24, scale=0), autoincrement=False, nullable=False), sa.Column('date', sa.Date(), nullable=False), sa.Column('from_user', sa.String(length=15), nullable=True), sa.Column('registered', sa.Boolean(), server_default='False', nullable=False), @@ -62,7 +62,7 @@ def upgrade(): sa.PrimaryKeyConstraint('id') ) op.create_table('item', - sa.Column('id', sa.BigInteger(), nullable=False), + sa.Column('id', sa.BigInteger(), autoincrement=False, nullable=False), sa.Column('name', sa.String(length=64), nullable=False), sa.Column('brand', sa.Integer(), nullable=False), sa.Column('description', sa.Text(), nullable=False), @@ -94,7 +94,7 @@ def upgrade(): sa.PrimaryKeyConstraint('item', 'category') ) op.create_table('item_receipt', - sa.Column('receipt', sa.Numeric(precision=22, scale=0), nullable=False), + sa.Column('receipt', sa.Numeric(precision=24, scale=0), nullable=False), sa.Column('item', sa.BigInteger(), nullable=False), sa.Column('amount', sa.SmallInteger(), nullable=False), sa.ForeignKeyConstraint(['item'], ['item.id'], ), diff --git a/requirements.txt b/requirements.txt index 838b520..ad6c1a2 100644 Binary files a/requirements.txt and b/requirements.txt differ diff --git a/run.py b/run.py index 184630f..7a99fda 100644 --- a/run.py +++ b/run.py @@ -1,14 +1,9 @@ from app import create_app, db from app.models import * -from gevent.pywsgi import WSGIServer app = create_app() @app.shell_context_processor def make_shell_context(): return {'db': db, 'User': User, 'Bought': Bought, 'Item': Item, - "LoginToken": LoginToken, "Establishment": Establishment, "Receipt": Receipt} - -if __name__ == '__main__': - http_server = WSGIServer(('', 5000), app) - http_server.serve_forever() + "LoginToken": LoginToken, "Establishment": Establishment, "Receipt": Receipt} \ No newline at end of file