From d52e5a63d5ebc14042b505df56b9f8781f437536 Mon Sep 17 00:00:00 2001 From: Lunaresk Date: Mon, 17 Jan 2022 12:54:24 +0100 Subject: [PATCH 1/2] Working on better folder structure Trying to get a better maintainability by splitting the code in better folders. --- server/src/app/__init__.py | 5 ++ server/src/app/database.py | 141 +++++++++++++++++++++++++++++++++++++ server/src/app/views.py | 92 ++++++++++++++++++++++++ server/src/pagebuilder.py | 0 server/src/run.py | 6 ++ 5 files changed, 244 insertions(+) create mode 100644 server/src/app/__init__.py create mode 100644 server/src/app/database.py create mode 100644 server/src/app/views.py create mode 100644 server/src/pagebuilder.py create mode 100644 server/src/run.py diff --git a/server/src/app/__init__.py b/server/src/app/__init__.py new file mode 100644 index 0000000..b9357d3 --- /dev/null +++ b/server/src/app/__init__.py @@ -0,0 +1,5 @@ +from flask import Flask + +app = Flask(__name__) + +from app import views \ No newline at end of file diff --git a/server/src/app/database.py b/server/src/app/database.py new file mode 100644 index 0000000..e2e51c3 --- /dev/null +++ b/server/src/app/database.py @@ -0,0 +1,141 @@ +from os.path import dirname +from psycopg2 import connect as psyconn, ProgrammingError, errors +from yaml import safe_load +import logging + + +DIR = dirname(__file__) + "/" + +LOGGER = logging.getLogger(__name__) +LOGGER.setLevel(logging.DEBUG) +logFormatter = logging.Formatter( + "%(asctime)s [%(threadName)s] [%(levelname)s] %(message)s") + +fileHandler = logging.FileHandler(DIR + "../../logs/database.log") +fileHandler.setFormatter(logFormatter) +fileHandler.setLevel(logging.INFO) +LOGGER.addHandler(fileHandler) + +consoleHandler = logging.StreamHandler() +consoleHandler.setLevel(logging.DEBUG) +LOGGER.addHandler(consoleHandler) + + +class Database: + + def __init__(self, **kwargs): + pass + + def connect(self, **kwargs): + with open(DIR + 'config.yaml', 'r') as file: + data = safe_load(file)['database'] + LOGGER.debug('Merging passed arguments with default arguments.') + for key, value in data.items(): + if key not in kwargs or not kwargs[key]: + kwargs[key] = value + LOGGER.info('Connecting to Database.') + self.conn = psyconn(host=kwargs["host"], port=kwargs["port"], dbname=kwargs["database"], + user=kwargs["user"], password=kwargs["password"]) + self.conn.autocommit = True + + def test_connection(self): + if not hasattr(self, "conn"): + LOGGER.info("Connection was not set, setting...") + self.connect() + else: + try: + with self.conn.cursor() as cursor: + try: + cursor.execute("SELECT 1;") + cursor.fetchall() + except: + LOGGER.warn( + 'Connection seem to timed out, reconnecting...') + self.connect() + except: + LOGGER.warn('Connection seem to timed out, reconnecting...') + self.connect() + + def connectionpersistence(func): + def wrapper(*args, **kwargs): + self = args[0] + self.test_connection() + return func(*args, **kwargs) + return wrapper + + @connectionpersistence + def get_user(self, **kwargs): + result = () + if 'login' in kwargs: + query = "SELECT login FROM users WHERE login = %(login)s;" + with self.conn.cursor() as cursor: + cursor.execute(query, kwargs) + try: + result = cursor.fetchall() + except ProgrammingError as e: + LOGGER.exception("") + except Exception as e: + LOGGER.exception("") + return result + + @connectionpersistence + def get_report(self, **kwargs) -> list: + query = "SELECT u.name, bp.date, i.name, bp.amount, bp.price FROM bought_with_prices bp INNER JOIN items i ON bp.item = i.id INNER JOIN users u ON bp.user = u.login" + if kwargs: + query += " WHERE " + tempquery = [] + if "user" in kwargs and kwargs['user']: + tempquery.append(f"bp.user = '{kwargs['user']}'") + if "year" in kwargs and kwargs['year']: + tempstring = "bp.date BETWEEN " + if "month" in kwargs and kwargs['month']: + tempstring += f"'{kwargs['year']}-{kwargs['month']}-01' AND " + tempstring += f"'{kwargs['year']+1}-01-01'" if kwargs['month'] == 12 else f"'{kwargs['year']}-{kwargs['month']+1}-01'" + else: + tempstring += f"'{kwargs['year']}-01-01' AND '{kwargs['year']+1}-01-01'" + tempstring += "::date - INTERVAL '1' DAY" + tempquery.append(tempstring) + query += " AND ".join(tempquery) + query += " ORDER BY u.name, bp.date, i.name ASC;" + LOGGER.debug(f"Executing query: {query}") + result = [] + with self.conn.cursor() as cursor: + cursor.execute(query) + try: + result = cursor.fetchall() + except ProgrammingError as e: + LOGGER.exception("") + except Exception as e: + LOGGER.exception("") + return result + + @connectionpersistence + def insert_bought_items(self, user: str, items: dict, date: str = None): + temp = ['"user", item, amount', "%(user)s, %(item)s, %(amount)s", + "bought.user = %(user)s AND bought.item = %(item)s AND bought.date = " + ("%(date)s" if date else "NOW()::date")] + if date: + temp[0] += ", date" + temp[1] += ", %(date)s" + values = [{'user': user, 'item': int(key), 'amount': value, 'date': date} for key, value in items.items()] + else: + values = [{'user': user, 'item': int(key), 'amount': value} for key, value in items.items()] + query = f"INSERT INTO bought({temp[0]}) VALUES({temp[1]}) ON CONFLICT ON CONSTRAINT bought_user_item_date DO UPDATE SET amount = bought.amount + %(amount)s WHERE {temp[2]};" + with self.conn.cursor() as cursor: + failed = {} + for value in values: + try: + cursor.execute(query, value) + except errors.ForeignKeyViolation as e: + if failed: + failed['items'][value['item']] = value['amount'] + else: + failed = {'user': user, 'items': {value['item']: value['amount']}} + if date: + failed['date'] = date + LOGGER.exception("") + except Exception as e: + LOGGER.exception("") + return failed + + def __delete__(self): + self.conn.close() diff --git a/server/src/app/views.py b/server/src/app/views.py new file mode 100644 index 0000000..2db08b6 --- /dev/null +++ b/server/src/app/views.py @@ -0,0 +1,92 @@ +from app import app +from app.database import Database +from flask import abort, request +from flask.json import jsonify +from os import makedirs +from os.path import dirname, exists +import logging + + +DIR = dirname(__file__) + "/" + +if not exists(DIR + "../logs"): + makedirs(DIR + "../logs") + +LOGGER = logging.getLogger(__name__) +LOGGER.setLevel(logging.DEBUG) +logFormatter = logging.Formatter( + "%(asctime)s [%(threadName)s] [%(levelname)s] %(message)s") + +fileHandler = logging.FileHandler(DIR + "../../logs/server.log") +fileHandler.setFormatter(logFormatter) +fileHandler.setLevel(logging.INFO) +LOGGER.addHandler(fileHandler) + +consoleHandler = logging.StreamHandler() +consoleHandler.setLevel(logging.DEBUG) +LOGGER.addHandler(consoleHandler) + +DATABASE = Database() + +APPNAME = "scan2kasse" + + +@app.route('/') +def index(): + return "

Hello, World!", 200 + + +@app.route(f'/{APPNAME}/login') +def login(): + if not request.json or 'login' not in request.json: + abort(400) + if not DATABASE.get_user(login = request.json['login']): + abort(403) + return jsonify({}), 200 + + +@app.route(f'/{APPNAME}/insert', methods=['POST']) +def insert(): + match request.json: + case {'user': user, 'items': items, 'date': date}: + failed = DATABASE.insert_bought_items(user, items, date) + if failed: + return jsonify(failed), 400 + return jsonify({'inserted': True}), 201 + case {'user': user, 'items': items}: + failed = DATABASE.insert_bought_items(user, items) + if failed: + return jsonify(failed), 400 + return jsonify({'inserted': True}), 201 + case _: + abort(400) + +@app.route(f'/{APPNAME}/', methods=['GET']) +@app.route(f'/{APPNAME}//', methods=['GET']) +@app.route(f'/{APPNAME}//', methods=['GET']) +@app.route(f'/{APPNAME}///', methods=['GET']) +def get_monthly_report_from_user(user: str = None, year: int = None, month: int = None): + if month and (month > 12 or month < 1): + abort(400) + LOGGER.info("Getting results.") + results = DATABASE.get_report(user=user, year=year, month=month) + LOGGER.debug(f"Results received: {results}") + if results: + result_dict = group_results(results) + else: + result_dict = {} + return jsonify(result_dict) + + +def group_results(results: tuple) -> dict: + result_dict = {} + LOGGER.debug("Grouping...") + for result in results: + if result[0] not in result_dict: + result_dict[result[0]] = {} + if str(result[1]) not in result_dict[result[0]]: + result_dict[result[0]][str(result[1])] = {} + result_dict[result[0]][str(result[1])][result[2]] = ( + result[3], result[4]) + LOGGER.debug("Grouped.") + return result_dict \ No newline at end of file diff --git a/server/src/pagebuilder.py b/server/src/pagebuilder.py new file mode 100644 index 0000000..e69de29 diff --git a/server/src/run.py b/server/src/run.py new file mode 100644 index 0000000..858949c --- /dev/null +++ b/server/src/run.py @@ -0,0 +1,6 @@ +from app import app +from gevent.pywsgi import WSGIServer + +if __name__ == '__main__': + http_server = WSGIServer(('', 5000), app) + http_server.serve_forever() From fd75f419e825ae0af54499ac2df85ccf49ef6d9d Mon Sep 17 00:00:00 2001 From: Lunaresk Date: Mon, 17 Jan 2022 20:19:19 +0100 Subject: [PATCH 2/2] patch: better overview for datas --- .gitignore | 3 +- server/src/app/templates/base.html | 15 +++ server/src/app/templates/overview.html | 27 +++++ server/src/app/views.py | 10 +- server/src/database.py | 141 ------------------------- server/src/main.py | 96 ----------------- server/src/pagebuilder.py | 0 7 files changed, 51 insertions(+), 241 deletions(-) create mode 100644 server/src/app/templates/base.html create mode 100644 server/src/app/templates/overview.html delete mode 100644 server/src/database.py delete mode 100644 server/src/main.py delete mode 100644 server/src/pagebuilder.py diff --git a/.gitignore b/.gitignore index 42dca0e..e6b9528 100644 --- a/.gitignore +++ b/.gitignore @@ -130,4 +130,5 @@ dmypy.json # Misc config.yaml -scans.json \ No newline at end of file +scans.json +test.* \ No newline at end of file diff --git a/server/src/app/templates/base.html b/server/src/app/templates/base.html new file mode 100644 index 0000000..3f54057 --- /dev/null +++ b/server/src/app/templates/base.html @@ -0,0 +1,15 @@ + + + + + + + + + +
+ {% block content %}{% endblock %} +
+ + + \ No newline at end of file diff --git a/server/src/app/templates/overview.html b/server/src/app/templates/overview.html new file mode 100644 index 0000000..5930484 --- /dev/null +++ b/server/src/app/templates/overview.html @@ -0,0 +1,27 @@ +{% extends "base.html" %} + +{% block content %} + {% for user, dates in results.items() %} +
+

{{ user }}: {{ dates.sum }}€

+ {% for date, items in dates.items() %} + {% if date != "sum" %} +
+
+
+

{{ date }}

+ {% for item, values in items.items() %} +
+
+
+ {{ values.0 }}x {{ item }} je {{ values.1 }}€ +
+
+ {% endfor %} +
+
+ {% endif %} + {% endfor %} +
+ {% endfor %} +{% endblock %} \ No newline at end of file diff --git a/server/src/app/views.py b/server/src/app/views.py index 2db08b6..a8239d8 100644 --- a/server/src/app/views.py +++ b/server/src/app/views.py @@ -1,6 +1,6 @@ from app import app from app.database import Database -from flask import abort, request +from flask import abort, request, render_template from flask.json import jsonify from os import makedirs from os.path import dirname, exists @@ -75,7 +75,10 @@ def get_monthly_report_from_user(user: str = None, year: int = None, month: int result_dict = group_results(results) else: result_dict = {} - return jsonify(result_dict) + if request.content_type == "application/json": + return jsonify(result_dict) + else: + return render_template("overview.html", results=result_dict) def group_results(results: tuple) -> dict: @@ -83,10 +86,11 @@ def group_results(results: tuple) -> dict: LOGGER.debug("Grouping...") for result in results: if result[0] not in result_dict: - result_dict[result[0]] = {} + result_dict[result[0]] = {"sum": 0} if str(result[1]) not in result_dict[result[0]]: result_dict[result[0]][str(result[1])] = {} result_dict[result[0]][str(result[1])][result[2]] = ( result[3], result[4]) + result_dict[result[0]]["sum"] += (result[3] * (result[4] * 100)) / 100 LOGGER.debug("Grouped.") return result_dict \ No newline at end of file diff --git a/server/src/database.py b/server/src/database.py deleted file mode 100644 index bd9f083..0000000 --- a/server/src/database.py +++ /dev/null @@ -1,141 +0,0 @@ -from os.path import dirname -from psycopg2 import connect as psyconn, ProgrammingError, errors -from yaml import safe_load -import logging - - -DIR = dirname(__file__) + "/" - -LOGGER = logging.getLogger(__name__) -LOGGER.setLevel(logging.DEBUG) -logFormatter = logging.Formatter( - "%(asctime)s [%(threadName)s] [%(levelname)s] %(message)s") - -fileHandler = logging.FileHandler(DIR + "../logs/database.log") -fileHandler.setFormatter(logFormatter) -fileHandler.setLevel(logging.INFO) -LOGGER.addHandler(fileHandler) - -consoleHandler = logging.StreamHandler() -consoleHandler.setLevel(logging.DEBUG) -LOGGER.addHandler(consoleHandler) - - -class Database: - - def __init__(self, **kwargs): - pass - - def connect(self, **kwargs): - with open(DIR + 'config.yaml', 'r') as file: - data = safe_load(file)['database'] - LOGGER.debug('Merging passed arguments with default arguments.') - for key, value in data.items(): - if key not in kwargs or not kwargs[key]: - kwargs[key] = value - LOGGER.info('Connecting to Database.') - self.conn = psyconn(host=kwargs["host"], port=kwargs["port"], dbname=kwargs["database"], - user=kwargs["user"], password=kwargs["password"]) - self.conn.autocommit = True - - def test_connection(self): - if not hasattr(self, "conn"): - LOGGER.info("Connection was not set, setting...") - self.connect() - else: - try: - with self.conn.cursor() as cursor: - try: - cursor.execute("SELECT 1;") - cursor.fetchall() - except: - LOGGER.warn( - 'Connection seem to timed out, reconnecting...') - self.connect() - except: - LOGGER.warn('Connection seem to timed out, reconnecting...') - self.connect() - - def connectionpersistence(func): - def wrapper(*args, **kwargs): - self = args[0] - self.test_connection() - return func(*args, **kwargs) - return wrapper - - @connectionpersistence - def get_user(self, **kwargs): - result = () - if 'login' in kwargs: - query = "SELECT login FROM users WHERE login = %(login)s;" - with self.conn.cursor() as cursor: - cursor.execute(query, kwargs) - try: - result = cursor.fetchall() - except ProgrammingError as e: - LOGGER.exception("") - except Exception as e: - LOGGER.exception("") - return result - - @connectionpersistence - def get_report(self, **kwargs) -> list: - query = "SELECT u.name, bp.date, i.name, bp.amount, bp.price FROM bought_with_prices bp INNER JOIN items i ON bp.item = i.id INNER JOIN users u ON bp.user = u.login" - if kwargs: - query += " WHERE " - tempquery = [] - if "user" in kwargs and kwargs['user']: - tempquery.append(f"bp.user = '{kwargs['user']}'") - if "year" in kwargs and kwargs['year']: - tempstring = "bp.date BETWEEN " - if "month" in kwargs and kwargs['month']: - tempstring += f"'{kwargs['year']}-{kwargs['month']}-01' AND " - tempstring += f"'{kwargs['year']+1}-01-01'" if kwargs['month'] == 12 else f"'{kwargs['year']}-{kwargs['month']+1}-01'" - else: - tempstring += f"'{kwargs['year']}-01-01' AND '{kwargs['year']+1}-01-01'" - tempstring += "::date - INTERVAL '1' DAY" - tempquery.append(tempstring) - query += " AND ".join(tempquery) - query += " ORDER BY u.name, bp.date, i.name ASC;" - LOGGER.debug(f"Executing query: {query}") - result = [] - with self.conn.cursor() as cursor: - cursor.execute(query) - try: - result = cursor.fetchall() - except ProgrammingError as e: - LOGGER.exception("") - except Exception as e: - LOGGER.exception("") - return result - - @connectionpersistence - def insert_bought_items(self, user: str, items: dict, date: str = None): - temp = ['"user", item, amount', "%(user)s, %(item)s, %(amount)s", - "bought.user = %(user)s AND bought.item = %(item)s AND bought.date = " + ("%(date)s" if date else "NOW()::date")] - if date: - temp[0] += ", date" - temp[1] += ", %(date)s" - values = [{'user': user, 'item': int(key), 'amount': value, 'date': date} for key, value in items.items()] - else: - values = [{'user': user, 'item': int(key), 'amount': value} for key, value in items.items()] - query = f"INSERT INTO bought({temp[0]}) VALUES({temp[1]}) ON CONFLICT ON CONSTRAINT bought_user_item_date DO UPDATE SET amount = bought.amount + %(amount)s WHERE {temp[2]};" - with self.conn.cursor() as cursor: - failed = {} - for value in values: - try: - cursor.execute(query, value) - except errors.ForeignKeyViolation as e: - if failed: - failed['items'][value['item']] = value['amount'] - else: - failed = {'user': user, 'items': {value['item']: value['amount']}} - if date: - failed['date'] = date - LOGGER.exception("") - except Exception as e: - LOGGER.exception("") - return failed - - def __delete__(self): - self.conn.close() diff --git a/server/src/main.py b/server/src/main.py deleted file mode 100644 index 719796d..0000000 --- a/server/src/main.py +++ /dev/null @@ -1,96 +0,0 @@ -from database import Database -from flask import Flask, abort, request -from flask.json import jsonify -from gevent.pywsgi import WSGIServer -from os import makedirs -from os.path import dirname, exists -import logging - - -DIR = dirname(__file__) + "/" - -if not exists(DIR + "../logs"): - makedirs(DIR + "../logs") - -LOGGER = logging.getLogger(__name__) -LOGGER.setLevel(logging.DEBUG) -logFormatter = logging.Formatter( - "%(asctime)s [%(threadName)s] [%(levelname)s] %(message)s") - -fileHandler = logging.FileHandler(DIR + "../logs/server.log") -fileHandler.setFormatter(logFormatter) -fileHandler.setLevel(logging.INFO) -LOGGER.addHandler(fileHandler) - -consoleHandler = logging.StreamHandler() -consoleHandler.setLevel(logging.DEBUG) -LOGGER.addHandler(consoleHandler) - -app = Flask(__name__) -DATABASE = Database() - - -@app.route('/') -def index(): - return "

Hello, World!", 200 - - -@app.route('/scan2kasse/login') -def login(): - if not request.json or 'login' not in request.json: - abort(400) - if not DATABASE.get_user(login = request.json['login']): - abort(403) - return jsonify({}), 200 - - -@app.route('/scan2kasse/insert', methods=['POST']) -def insert(): - match request.json: - case {'user': user, 'items': items, 'date': date}: - failed = DATABASE.insert_bought_items(user, items, date) - if failed: - return jsonify(failed), 400 - return jsonify({'inserted': True}), 201 - case {'user': user, 'items': items}: - failed = DATABASE.insert_bought_items(user, items) - if failed: - return jsonify(failed), 400 - return jsonify({'inserted': True}), 201 - case _: - abort(400) - -@app.route('/scan2kasse/', methods=['GET']) -@app.route('/scan2kasse//', methods=['GET']) -@app.route('/scan2kasse//', methods=['GET']) -@app.route('/scan2kasse///', methods=['GET']) -def get_monthly_report_from_user(user: str = None, year: int = None, month: int = None): - if month and (month > 12 or month < 1): - abort(400) - LOGGER.info("Getting results.") - results = DATABASE.get_report(user=user, year=year, month=month) - LOGGER.debug(f"Results received: {results}") - if results: - result_dict = group_results(results) - else: - result_dict = {} - return jsonify(result_dict) - - -def group_results(results: tuple) -> dict: - result_dict = {} - LOGGER.debug("Grouping...") - for result in results: - if result[0] not in result_dict: - result_dict[result[0]] = {} - if str(result[1]) not in result_dict[result[0]]: - result_dict[result[0]][str(result[1])] = {} - result_dict[result[0]][str(result[1])][result[2]] = ( - result[3], result[4]) - LOGGER.debug("Grouped.") - return result_dict - - -if __name__ == '__main__': - http_server = WSGIServer(('', 5000), app) - http_server.serve_forever() diff --git a/server/src/pagebuilder.py b/server/src/pagebuilder.py deleted file mode 100644 index e69de29..0000000