diff --git a/application/access_module/__init__.py b/application/access_module/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/user_api.py b/application/access_module/access_api.py similarity index 63% rename from user_api.py rename to application/access_module/access_api.py index 967b17a..d73e4b1 100644 --- a/user_api.py +++ b/application/access_module/access_api.py @@ -1,12 +1,18 @@ from flask import Blueprint, request, render_template, redirect, session, url_for, jsonify -import hashlib, psycopg2, process, MyDataclasses -from config import config, sites_config, setFirstSetupDone +from authlib.integrations.flask_client import OAuth +import hashlib, psycopg2 +from config import config, sites_config from functools import wraps -from manage import create -from main import create_site, getUser, setSystemAdmin import postsqldb +import requests + +from application.access_module import access_database +from outh import oauth + +access_api = Blueprint('access_api', __name__, template_folder="templates", static_folder="static") + + -login_app = Blueprint('login', __name__) def update_session_user(): database_config = config() @@ -18,45 +24,44 @@ def login_required(func): @wraps(func) def wrapper(*args, **kwargs): if 'user' not in session or session['user'] == None: - return redirect(url_for('login.login')) + return redirect(url_for('access_api.login')) return func(*args, **kwargs) return wrapper - -@login_app.route('/setup', methods=['GET', 'POST']) -def first_time_setup(): - if request.method == "POST": - database_address = request.form['database_address'] - database_port = request.form['database_port'] - database_name = request.form['database_name'] - database_user = request.form['database_user'] - database_password = request.form['database_address'] - - site_manager = MyDataclasses.SiteManager( - site_name=request.form['site_name'], - admin_user=(request.form['username'], hashlib.sha256(request.form['password'].encode()).hexdigest(), request.form['email']), - default_zone=request.form['site_default_zone'], - default_location=request.form['site_default_location'], - description=request.form['site_description'] - ) - - process.addSite(site_manager) - - setFirstSetupDone() - - return redirect("/login") - - return render_template("setup.html") - - - -@login_app.route('/logout', methods=['GET']) +@access_api.route('/logout', methods=['GET']) +@login_required def logout(): if 'user' in session.keys(): session['user'] = None - return redirect('/login') + return redirect('/access/login') -@login_app.route('/login', methods=['POST', 'GET']) +@access_api.route('/auth') +def auth(): + token = oauth.authentik.authorize_access_token() + access_token = token['access_token'] + userinfo_endpoint="https://auth.treehousefullofstars.com/application/o/userinfo/" + headers = { + 'Authorization': f'Bearer {access_token}', + } + response = requests.get(userinfo_endpoint, headers=headers) + if response.status_code == 200: + user_email = response.json()['email'] + user = access_database.selectUserByEmail((user_email,)) + user = access_database.washUserDictionary(user) + session['user_id'] = user['id'] + session['user'] = user + session['login_type'] = 'External' + return redirect('/') + else: + print("Failed to fetch user info:", response.status_code, response.text) + return redirect('/access/login') + +@access_api.route('/login/oidc') +def oidc_login(): + redirect_uri = url_for('access_api.auth', _external=True) + return oauth.authentik.authorize_redirect(redirect_uri) + +@access_api.route('/login', methods=['POST', 'GET']) def login(): session.clear() instance_config = sites_config() @@ -83,6 +88,7 @@ def login(): if user and user[2] == password: session['user_id'] = user[0] session['user'] = {'id': user[0], 'username': user[1], 'sites': user[13], 'site_roles': user[14], 'system_admin': user[15], 'flags': user[16]} + session['login_type'] = 'Internal' return jsonify({'error': False, 'message': 'Logged In Sucessfully!'}) else: return jsonify({'error': True, 'message': 'Username or Password was incorrect!'}) @@ -91,9 +97,15 @@ def login(): if 'user' not in session.keys(): session['user'] = None - return render_template("other/login.html") + return render_template("login.html") -@login_app.route('/signup', methods=['POST', 'GET']) +@access_api.route('/dashboard') +def dashboard(): + if 'user' not in session: + return redirect('/') + return f"Hello, {session['user']['name']}! Logout" + +@access_api.route('/signup', methods=['POST', 'GET']) def signup(): instance_config = sites_config() if not instance_config['signup_enabled']: diff --git a/application/access_module/access_database.py b/application/access_module/access_database.py new file mode 100644 index 0000000..d7f0818 --- /dev/null +++ b/application/access_module/access_database.py @@ -0,0 +1,42 @@ +import psycopg2 + +import config +from application import postsqldb + +def washUserDictionary(user): + return { + 'id': user['id'], + 'username': user['username'], + 'sites': user['sites'], + 'site_roles': user['site_roles'], + 'system_admin': user['system_admin'], + 'flags': user['flags'] + } + +def selectUserByEmail(payload, convert=True, conn=None): + """ payload = (email,)""" + self_conn = False + user = () + sql = f"SELECT * FROM logins WHERE email=%s;" + try: + if not conn: + database_config = config.config() + conn = psycopg2.connect(**database_config) + conn.autocommit = True + self_conn = True + + with conn.cursor() as cur: + cur.execute(sql, payload) + rows = cur.fetchone() + if rows and convert: + user = postsqldb.tupleDictionaryFactory(cur.description, rows) + elif rows and not convert: + user = rows + + if self_conn: + conn.commit() + conn.close() + + return user + except Exception as error: + raise postsqldb.DatabaseError(error, payload, sql) \ No newline at end of file diff --git a/application/access_module/access_processes.py b/application/access_module/access_processes.py new file mode 100644 index 0000000..e69de29 diff --git a/static/handlers/loginHandler.js b/application/access_module/static/js/loginHandler.js similarity index 97% rename from static/handlers/loginHandler.js rename to application/access_module/static/js/loginHandler.js index 99e8216..a9576af 100644 --- a/static/handlers/loginHandler.js +++ b/application/access_module/static/js/loginHandler.js @@ -8,7 +8,7 @@ async function loginUser() { let username = document.getElementById('login_username').value let password = document.getElementById('login_password').value - const response = await fetch(`/login`, { + const response = await fetch(`/access/login`, { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -97,7 +97,7 @@ async function signupUser() { let user_email = document.getElementById('signup_email').value let password = document.getElementById('signup_password').value let username = document.getElementById('signup_username').value - const response = await fetch(`/signup`, { + const response = await fetch(`/access/signup`, { method: 'POST', headers: { 'Content-Type': 'application/json', diff --git a/templates/other/login.html b/application/access_module/templates/login.html similarity index 94% rename from templates/other/login.html rename to application/access_module/templates/login.html index bd77adb..97353da 100644 --- a/templates/other/login.html +++ b/application/access_module/templates/login.html @@ -35,6 +35,9 @@
+
@@ -98,5 +101,5 @@
- + \ No newline at end of file diff --git a/application/administration/administration_api.py b/application/administration/administration_api.py index 6c00eed..e9432d5 100644 --- a/application/administration/administration_api.py +++ b/application/administration/administration_api.py @@ -1,13 +1,14 @@ # 3RD PARTY IMPORTS -from flask import (Blueprint, request, render_template, session, jsonify) +from flask import ( + Blueprint, request, render_template, session, jsonify, redirect + ) import math import hashlib - # APPLICATION IMPORTS +from application.access_module import access_api from application.administration import administration_database, administration_processes from application import database_payloads, postsqldb -from user_api import login_required admin_api = Blueprint('admin_api', __name__, template_folder="templates", static_folder="static") @@ -15,13 +16,13 @@ admin_api = Blueprint('admin_api', __name__, template_folder="templates", static # ROOT TEMPLATE ROUTES @admin_api.route('/') +@access_api.login_required def admin_index(): sites = [site[1] for site in postsqldb.get_sites(session['user']['sites'])] return render_template("admin_index.html", current_site=session['selected_site'], sites=sites) -# Added to Database @admin_api.route('/site/') -@login_required +@access_api.login_required def adminSites(id): if id == "new": new_site_payload = database_payloads.SitePayload("", "", session['user_id']) @@ -30,9 +31,8 @@ def adminSites(id): site = administration_database.selectSitesTuple((id,)) return render_template('site.html', site=site) -# Added to database @admin_api.route('/role/') -@login_required +@access_api.login_required def adminRoles(id): sites = administration_database.selectSitesTuples() if id == "new": @@ -42,9 +42,8 @@ def adminRoles(id): role = administration_database.selectRolesTuple((id,)) return render_template('role.html', role=role, sites=sites) -# Added to database @admin_api.route('/user/') -@login_required +@access_api.login_required def adminUser(id): if id == "new": new_user_payload = database_payloads.LoginsPayload("", "", "", "") @@ -53,10 +52,32 @@ def adminUser(id): user = administration_database.selectLoginsTuple((int(id),)) return render_template('user.html', user=user) +@admin_api.route('/setup', methods=['GET', 'POST']) +def first_time_setup(): + if request.method == "POST": + database_address = request.form['database_address'] + database_port = request.form['database_port'] + database_name = request.form['database_name'] + database_user = request.form['database_user'] + database_password = request.form['database_address'] + + payload = { + "site_name" : request.form['site_name'], + "admin_user": (request.form['username'], hashlib.sha256(request.form['password'].encode()).hexdigest(), request.form['email']), + "default_zone": request.form['site_default_zone'], + "default_primary_location": request.form['site_default_location'], + "site_description": request.form['site_description'] + } + + administration_processes.addSite(payload) + + return redirect("/login") + + return render_template("setup.html") + # API ROUTES -# add to database @admin_api.route('/api/getSites', methods=['GET']) -@login_required +@access_api.login_required def getSites(): if request.method == "GET": records = [] @@ -68,9 +89,8 @@ def getSites(): return jsonify({'sites': records, "end": math.ceil(count/limit), 'error':False, 'message': 'Sites Loaded Successfully!'}) return jsonify({'sites': records, "end": math.ceil(count/limit), 'error':True, 'message': 'There was a problem loading Sites!'}) -# Added to database @admin_api.route('/api/getRoles', methods=['GET']) -@login_required +@access_api.login_required def getRoles(): if request.method == "GET": records = [] @@ -82,9 +102,8 @@ def getRoles(): return jsonify({'roles': records, "end": math.ceil(count/limit), 'error':False, 'message': 'Roles Loaded Successfully!'}) return jsonify({'roles': records, "end": math.ceil(count/limit), 'error':True, 'message': 'There was a problem loading Roles!'}) -# Added to Database @admin_api.route('/api/getLogins', methods=['GET']) -@login_required +@access_api.login_required def getLogins(): if request.method == "GET": records = [] @@ -96,8 +115,8 @@ def getLogins(): return jsonify({'logins': records, "end": math.ceil(count/limit), 'error':False, 'message': 'logins Loaded Successfully!'}) return jsonify({'logins': records, "end": math.ceil(count/limit), 'error':True, 'message': 'There was a problem loading logins!'}) -# Added to database and Processses. @admin_api.route('/api/site/postDeleteSite', methods=["POST"]) +@access_api.login_required def postDeleteSite(): if request.method == "POST": site_id = request.get_json()['site_id'] @@ -115,8 +134,8 @@ def postDeleteSite(): return jsonify({'error': False, 'message': f""}) return jsonify({'error': True, 'message': f""}) -# Added to Database and Processes @admin_api.route('/api/site/postAddSite', methods=["POST"]) +@access_api.login_required def postAddSite(): if request.method == "POST": payload = request.get_json()['payload'] @@ -131,8 +150,8 @@ def postAddSite(): return jsonify({'error': False, 'message': f"Zone added to {site_name}."}) return jsonify({'error': True, 'message': f"These was an error with adding this Zone to {site_name}."}) -# Added to Database @admin_api.route('/api/site/postEditSite', methods=["POST"]) +@access_api.login_required def postEditSite(): if request.method == "POST": payload = request.get_json()['payload'] @@ -140,8 +159,8 @@ def postEditSite(): return jsonify({'error': False, 'message': f"Site updated."}) return jsonify({'error': True, 'message': f"These was an error with updating Site."}) -# Added to Database @admin_api.route('/api/role/postAddRole', methods=["POST"]) +@access_api.login_required def postAddRole(): if request.method == "POST": payload = request.get_json()['payload'] @@ -155,8 +174,8 @@ def postAddRole(): return jsonify({'error': False, 'message': f"Role added."}) return jsonify({'error': True, 'message': f"These was an error with adding this Role."}) -# Added to Database @admin_api.route('/api/role/postEditRole', methods=["POST"]) +@access_api.login_required def postEditRole(): if request.method == "POST": payload = request.get_json()['payload'] @@ -164,8 +183,8 @@ def postEditRole(): return jsonify({'error': False, 'message': f"Role updated."}) return jsonify({'error': True, 'message': f"These was an error with updating this Role."}) -# Added to database @admin_api.route('/api/user/postAddLogin', methods=["POST"]) +@access_api.login_required def postAddLogin(): if request.method == "POST": payload = request.get_json()['payload'] @@ -180,8 +199,8 @@ def postAddLogin(): return jsonify({'user': user, 'error': False, 'message': f"User added."}) return jsonify({'user': user, 'error': True, 'message': f"These was an error with adding this User."}) -# Added to database @admin_api.route('/api/user/postEditLogin', methods=["POST"]) +@access_api.login_required def postEditLogin(): if request.method == "POST": payload = request.get_json()['payload'] @@ -189,8 +208,8 @@ def postEditLogin(): return jsonify({'error': False, 'message': f"User was Added Successfully."}) return jsonify({'error': True, 'message': f"These was an error with adding this user."}) -# Added to Database @admin_api.route('/api/user/postEditLoginPassword', methods=["POST"]) +@access_api.login_required def postEditLoginPassword(): if request.method == "POST": payload = request.get_json()['payload'] diff --git a/templates/setup.html b/application/administration/templates/setup.html similarity index 100% rename from templates/setup.html rename to application/administration/templates/setup.html diff --git a/application/items/database_items.py b/application/items/database_items.py index 6415df2..cd8a710 100644 --- a/application/items/database_items.py +++ b/application/items/database_items.py @@ -1,19 +1,14 @@ -from application import postsqldb -import config +# 3RD PARTY IMPORTS import psycopg2 import datetime +# APPLICATION IMPORTS +from application import postsqldb +import config + + def getTransactions(site:str, payload: tuple, convert:bool=True): - """ Page through a sites Transactions by passing a logistics id, limit, and offset through a payload - - Args: - site (str): _description_ - payload (tuple): (logistics_id, limit, offset) - convert (bool, optional): _description_. Defaults to True. - - Returns: - _type_: _description_ - """ + """ payload (tuple): (logistics_id, limit, offset) """ database_config = config.config() sql = f"SELECT * FROM {site}_transactions WHERE logistics_info_id=%s LIMIT %s OFFSET %s;" sql_count = f"SELECT COUNT(*) FROM {site}_transactions WHERE logistics_info_id=%s;" @@ -241,48 +236,25 @@ def getItemLocations(site, payload, convert=True, conn=None): raise postsqldb.DatabaseError(error, payload, sql) def getItemInfoTuple(site:str, payload:tuple, convert=True): - """_summary_ - - Args: - conn (_type_): _description_ - site (_type_): _description_ - payload (_type_): (item_info_id,) - convert (bool, optional): _description_. Defaults to True. - - Raises: - DatabaseError: _description_ - - Returns: - _type_: _description_ - """ - selected = () - database_config = config.config() - sql = f"SELECT * FROM {site}_item_info WHERE id=%s;" - try: - with psycopg2.connect(**database_config) as conn: - with conn.cursor() as cur: - cur.execute(sql, payload) - rows = cur.fetchone() - if rows and convert: - selected = postsqldb.tupleDictionaryFactory(cur.description, rows) - elif rows and not convert: - selected = rows - return selected - except Exception as error: - raise postsqldb.DatabaseError(error, payload, sql) + """ payload (_type_): (item_info_id,) """ + selected = () + database_config = config.config() + sql = f"SELECT * FROM {site}_item_info WHERE id=%s;" + try: + with psycopg2.connect(**database_config) as conn: + with conn.cursor() as cur: + cur.execute(sql, payload) + rows = cur.fetchone() + if rows and convert: + selected = postsqldb.tupleDictionaryFactory(cur.description, rows) + elif rows and not convert: + selected = rows + return selected + except Exception as error: + raise postsqldb.DatabaseError(error, payload, sql) def selectItemLocationsTuple(site_name, payload, convert=True): - """select a single tuple from ItemLocations table for site_name - - Args: - conn (_T_connector@connect): - site_name (str): - payload (tuple): [item_id, location_id] - convert (bool): defaults to False, used to determine return of tuple/dict - - Returns: - tuple: the row that was returned from the table - """ + """ payload (tuple): [item_id, location_id] """ item_locations = () database_config = config.config() select_item_location_sql = f"SELECT * FROM {site_name}_item_locations WHERE part_id = %s AND location_id = %s;" @@ -300,17 +272,7 @@ def selectItemLocationsTuple(site_name, payload, convert=True): return error def selectCostLayersTuple(site_name, payload, convert=True): - """select a single or series of cost layers from the database for site_name - - Args: - conn (_T_connector@connect): - site_name (str): - payload (tuple): (item_locations_id, ) - convert (bool): defaults to False, used for determining return as tuple/dict - - Returns: - list: list of tuples/dict from the cost_layers table for site_name - """ + """ payload (tuple): (item_locations_id, ) """ cost_layers = () database_config = config.config() select_cost_layers_sql = f"SELECT cl.* FROM {site_name}_item_locations il JOIN {site_name}_cost_layers cl ON cl.id = ANY(il.cost_layers) where il.id=%s;" @@ -329,19 +291,7 @@ def selectCostLayersTuple(site_name, payload, convert=True): return error def selectSiteTuple(payload, convert=True): - """Select a single Site from sites using site_name - - Args: - conn (_T_connector@connect): Postgresql Connector - payload (tuple): (site_name,) - convert (bool, optional): determines if to return tuple as dictionary. Defaults to False. - - Raises: - DatabaseError: - - Returns: - tuple or dict: selected tuples - """ + """ payload (tuple): (site_name,) """ site = () database_config = config.config() select_site_sql = f"SELECT * FROM sites WHERE site_name = %s;" @@ -381,24 +331,24 @@ def paginateZonesBySku(site: str, payload: tuple, convert=True): raise postsqldb.DatabaseError(error, payload, sql) def paginateLocationsWithZone(site:str, payload:tuple, convert:bool=True): - recordset, count = (), 0 - database_config = config.config() - with open(f"application/items/sql/getLocationsWithZone.sql", "r+") as file: - sql = file.read().replace("%%site_name%%", site) - try: - with psycopg2.connect(**database_config) as conn: - with conn.cursor() as cur: - cur.execute(sql, payload) - rows = cur.fetchall() - if rows and convert: - recordset = [postsqldb.tupleDictionaryFactory(cur.description, row) for row in rows] - elif rows and not convert: - recordset = rows - cur.execute(f"SELECT COUNT(*) FROM {site}_locations;") - count = cur.fetchone()[0] - return recordset, count - except Exception as error: - raise postsqldb.DatabaseError(error, (), sql) + recordset, count = (), 0 + database_config = config.config() + with open(f"application/items/sql/getLocationsWithZone.sql", "r+") as file: + sql = file.read().replace("%%site_name%%", site) + try: + with psycopg2.connect(**database_config) as conn: + with conn.cursor() as cur: + cur.execute(sql, payload) + rows = cur.fetchall() + if rows and convert: + recordset = [postsqldb.tupleDictionaryFactory(cur.description, row) for row in rows] + elif rows and not convert: + recordset = rows + cur.execute(f"SELECT COUNT(*) FROM {site}_locations;") + count = cur.fetchone()[0] + return recordset, count + except Exception as error: + raise postsqldb.DatabaseError(error, (), sql) def paginateLocationsBySkuZone(site: str, payload: tuple, convert=True): database_config = config.config() @@ -471,20 +421,7 @@ def insertCostLayersTuple(site, payload, convert=True, conn=None): raise postsqldb.DatabaseError(error, payload, sql) def insertItemLocationsTuple(site, payload, convert=True, conn=None): - """insert payload into item_locations table for site - - Args: - conn (_T_connector@connect): Postgresql Connector - site (str): - payload (tuple): (part_id[int], location_id[int], quantity_on_hand[float], cost_layers[lst2pgarr]) - convert (bool, optional): Determines if to return tuple as dictionary. Defaults to False. - - Raises: - DatabaseError: - - Returns: - tuple or dict: inserted tuple - """ + """ payload (tuple): (part_id[int], location_id[int], quantity_on_hand[float], cost_layers[lst2pgarr]) """ location = () self_conn = False database_config = config.config() @@ -514,21 +451,8 @@ def insertItemLocationsTuple(site, payload, convert=True, conn=None): raise postsqldb.DatabaseError(error, payload, sql) def insertLogisticsInfoTuple(site, payload, convert=True, conn=None): - """insert payload into logistics_info table for site - - Args: - conn (_T_connector@connect): Postgresql Connector - site (str): - payload (tuple): (barcode[str], primary_location[str], auto_issue_location[str], dynamic_locations[jsonb], - location_data[jsonb], quantity_on_hand[float]) - convert (bool, optional): Determines if to return tuple as dictionary. Defaults to False. - - Raises: - DatabaseError: - - Returns: - tuple or dict: inserted tuple - """ + """ payload (tuple): (barcode[str], primary_location[str], auto_issue_location[str], dynamic_locations[jsonb], + location_data[jsonb], quantity_on_hand[float]) """ logistics_info = () self_conn = False @@ -559,21 +483,8 @@ def insertLogisticsInfoTuple(site, payload, convert=True, conn=None): raise postsqldb.DatabaseError(error, payload, sql) def insertItemInfoTuple(site, payload, convert=True, conn=None): - """inserts payload into the item_info table of site - - Args: - conn (_T_connector@connect): Postgresql Connector - site_name (str): - payload (tuple): (barcode[str], linked_items[lst2pgarr], shopping_lists[lst2pgarr], recipes[lst2pgarr], groups[lst2pgarr], - packaging[str], uom[str], cost[float], safety_stock[float], lead_time_days[float], ai_pick[bool]) - convert (bool optional): Determines if to return tuple as dictionary. DEFAULTS to False. - - Raises: - DatabaseError: - - Returns: - tuple or dict: inserted tuple - """ + """ payload (tuple): (barcode[str], linked_items[lst2pgarr], shopping_lists[lst2pgarr], recipes[lst2pgarr], groups[lst2pgarr], + packaging[str], uom[str], cost[float], safety_stock[float], lead_time_days[float], ai_pick[bool]) """ item_info = () self_conn = False with open(f"application/items/sql/insertItemInfoTuple.sql", "r+") as file: @@ -601,20 +512,7 @@ def insertItemInfoTuple(site, payload, convert=True, conn=None): raise postsqldb.DatabaseError(error, payload, sql) def insertFoodInfoTuple(site, payload, convert=True, conn=None): - """insert payload into food_info table for site - - Args: - conn (_T_connector@connect): Postgresql Connector - site (str): - payload (_type_): (ingrediants[lst2pgarr], food_groups[lst2pgarr], nutrients[jsonstr], expires[bool]) - convert (bool, optional): Determines if to return tuple as dictionary. Defaults to False. - - Raises: - DatabaseError: - - Returns: - tuple or dict: inserted tuple - """ + """ payload (_type_): (ingrediants[lst2pgarr], food_groups[lst2pgarr], nutrients[jsonstr], expires[bool]) """ food_info = () self_conn = False with open(f"application/items/sql/insertFoodInfoTuple.sql", "r+") as file: @@ -643,22 +541,9 @@ def insertFoodInfoTuple(site, payload, convert=True, conn=None): raise postsqldb.DatabaseError(error, payload, sql) def insertItemTuple(site, payload, convert=True, conn=None): - """insert payload into items table for site - - Args: - conn (_T_connector@connect): Postgresql Connector - site (str): - payload (tuple): (barcode[str], item_name[str], brand[int], description[str], + """ payload (tuple): (barcode[str], item_name[str], brand[int], description[str], tags[lst2pgarr], links[jsonb], item_info_id[int], logistics_info_id[int], - food_info_id[int], row_type[str], item_type[str], search_string[str]) - convert (bool, optional): Determines if to return tuple as a dictionary. Defaults to False. - - Raises: - DatabaseError: - - Returns: - tuple or dict: inserted tuple - """ + food_info_id[int], row_type[str], item_type[str], search_string[str]) """ item = () self_conn = False with open(f"application/items/sql/insertItemTuple.sql", "r+") as file: @@ -687,99 +572,66 @@ def insertItemTuple(site, payload, convert=True, conn=None): raise postsqldb.DatabaseError(error, payload, sql) def insertSKUPrefixtuple(site:str, payload:tuple, convert=True, conn=None): - """insert payload into zones table of site + """ payload (tuple): (name[str],) """ + prefix = () + self_conn = False + with open(f"application/items/sql/insertSKUPrefixTuple.sql", "r+") as file: + sql = file.read().replace("%%site_name%%", site) + try: + if not conn: + database_config = config.config() + conn = psycopg2.connect(**database_config) + conn.autocommit = True + self_conn = True - Args: - conn (_T_connector@connect): Postgresql Connector - site (str): - payload (tuple): (name[str],) - convert (bool, optional): Determines if to return tuple as dictionary. Defaults to False. + with conn.cursor() as cur: + cur.execute(sql, payload) + rows = cur.fetchone() + if rows and convert: + prefix = postsqldb.tupleDictionaryFactory(cur.description, rows) + elif rows and not convert: + prefix = rows - Raises: - DatabaseError: + if self_conn: + conn.commit() + conn.close() - Returns: - tuple or dict: inserted tuple - """ - prefix = () - self_conn = False - with open(f"application/items/sql/insertSKUPrefixTuple.sql", "r+") as file: - sql = file.read().replace("%%site_name%%", site) - try: - if not conn: - database_config = config.config() - conn = psycopg2.connect(**database_config) - conn.autocommit = True - self_conn = True - - with conn.cursor() as cur: - cur.execute(sql, payload) - rows = cur.fetchone() - if rows and convert: - prefix = postsqldb.tupleDictionaryFactory(cur.description, rows) - elif rows and not convert: - prefix = rows - - if self_conn: - conn.commit() - conn.close() - - return prefix - except Exception as error: - raise postsqldb.DatabaseError(error, payload, sql) + return prefix + except Exception as error: + raise postsqldb.DatabaseError(error, payload, sql) def insertConversionTuple(site: str, payload: list, convert=True, conn=None): - """insert into recipes table for site + """ payload (tuple): (item_id, uom_id, conversion_factor) """ + record = () + self_conn = False + with open(f"sql/INSERT/insertConversionsTuple.sql", "r+") as file: + sql = file.read().replace("%%site_name%%", site) + try: + if not conn: + database_config = config.config() + conn = psycopg2.connect(**database_config) + conn.autocommit = True + self_conn = True - Args: - conn (_T_connector@connect): Postgresql Connector - site (stre): - payload (tuple): (item_id, uom_id, conversion_factor) - convert (bool, optional): Determines if to return tuple as a dictionary. Defaults to False. + with conn.cursor() as cur: + cur.execute(sql, payload) + rows = cur.fetchone() + if rows and convert: + record = postsqldb.tupleDictionaryFactory(cur.description, rows) + elif rows and not convert: + record = rows - Raises: - DatabaseError: + if self_conn: + conn.commit() + conn.close() - Returns: - tuple or dict: inserted tuple - """ - record = () - self_conn = False - with open(f"sql/INSERT/insertConversionsTuple.sql", "r+") as file: - sql = file.read().replace("%%site_name%%", site) - try: - if not conn: - database_config = config.config() - conn = psycopg2.connect(**database_config) - conn.autocommit = True - self_conn = True - - with conn.cursor() as cur: - cur.execute(sql, payload) - rows = cur.fetchone() - if rows and convert: - record = postsqldb.tupleDictionaryFactory(cur.description, rows) - elif rows and not convert: - record = rows - - if self_conn: - conn.commit() - conn.close() - - return record - - except Exception as error: - raise postsqldb.DatabaseError(error, payload, sql) + return record + + except Exception as error: + raise postsqldb.DatabaseError(error, payload, sql) def postDeleteCostLayer(site_name, payload, convert=True, conn=None): - """ - payload (tuple): (table_to_delete_from, tuple_id) - Raises: - DatabaseError: - - Returns: - tuple or dict: deleted tuple - """ + """ payload (tuple): (table_to_delete_from, tuple_id) """ deleted = () self_conn = False sql = f"WITH deleted_rows AS (DELETE FROM {site_name}_cost_layers WHERE id IN ({','.join(['%s'] * len(payload))}) RETURNING *) SELECT * FROM deleted_rows;" @@ -808,21 +660,7 @@ def postDeleteCostLayer(site_name, payload, convert=True, conn=None): raise postsqldb.DatabaseError(error, payload, sql) def deleteConversionTuple(site_name: str, payload: tuple, convert=True, conn=None): - """This is a basic funtion to delete a tuple from a table in site with an id. All - tables in this database has id's associated with them. - - Args: - conn (_T_connector@connect): Postgresql Connector - site_name (str): - payload (tuple): (tuple_id,...) - convert (bool, optional): Determines if to return tuple as dictionary. Defaults to False. - - Raises: - DatabaseError: - - Returns: - tuple or dict: deleted tuple - """ + """ payload (tuple): (tuple_id,...) """ deleted = () self_conn = False sql = f"WITH deleted_rows AS (DELETE FROM {site_name}_conversions WHERE id IN ({','.join(['%s'] * len(payload))}) RETURNING *) SELECT * FROM deleted_rows;" @@ -851,93 +689,65 @@ def deleteConversionTuple(site_name: str, payload: tuple, convert=True, conn=Non raise postsqldb.DatabaseError(error, payload, sql) def updateConversionTuple(site:str, payload: dict, convert=True, conn=None): - """_summary_ + """ payload (dict): {'id': row_id, 'update': {... column_to_update: value_to_update_to...}} """ + updated = () + self_conn = False + set_clause, values = postsqldb.updateStringFactory(payload['update']) + values.append(payload['id']) + sql = f"UPDATE {site}_conversions SET {set_clause} WHERE id=%s RETURNING *;" + try: + if not conn: + database_config = config.config() + conn = psycopg2.connect(**database_config) + conn.autocommit = False + self_conn = True - Args: - conn (_T_connector@connect): Postgresql Connector - site (str): - table (str): - payload (dict): {'id': row_id, 'update': {... column_to_update: value_to_update_to...}} - convert (bool, optional): determines if to return tuple as dictionary. Defaults to False. + with conn.cursor() as cur: + cur.execute(sql, values) + rows = cur.fetchone() + if rows and convert: + updated = postsqldb.tupleDictionaryFactory(cur.description, rows) + elif rows and not convert: + updated = rows + + if self_conn: + conn.commit() + conn.close() - Raises: - DatabaseError: - - Returns: - tuple or dict: updated tuple - """ - updated = () - self_conn = False - set_clause, values = postsqldb.updateStringFactory(payload['update']) - values.append(payload['id']) - sql = f"UPDATE {site}_conversions SET {set_clause} WHERE id=%s RETURNING *;" - try: - if not conn: - database_config = config.config() - conn = psycopg2.connect(**database_config) - conn.autocommit = False - self_conn = True - - with conn.cursor() as cur: - cur.execute(sql, values) - rows = cur.fetchone() - if rows and convert: - updated = postsqldb.tupleDictionaryFactory(cur.description, rows) - elif rows and not convert: - updated = rows - - if self_conn: - conn.commit() - conn.close() - - return updated - except Exception as error: - raise postsqldb.DatabaseError(error, payload, sql) + return updated + except Exception as error: + raise postsqldb.DatabaseError(error, payload, sql) def updateItemInfoTuple(site:str, payload: dict, convert=True, conn=None): - """_summary_ + """ payload (dict): {'id': row_id, 'update': {... column_to_update: value_to_update_to...}} """ + updated = () + self_conn = False + set_clause, values = postsqldb.updateStringFactory(payload['update']) + values.append(payload['id']) + sql = f"UPDATE {site}_item_info SET {set_clause} WHERE id=%s RETURNING *;" + try: + if not conn: + database_config = config.config() + conn = psycopg2.connect(**database_config) + conn.autocommit = False + self_conn = True - Args: - conn (_T_connector@connect): Postgresql Connector - site (str): - table (str): - payload (dict): {'id': row_id, 'update': {... column_to_update: value_to_update_to...}} - convert (bool, optional): determines if to return tuple as dictionary. Defaults to False. + with conn.cursor() as cur: + cur.execute(sql, values) + rows = cur.fetchone() + if rows and convert: + updated = postsqldb.tupleDictionaryFactory(cur.description, rows) + elif rows and not convert: + updated = rows + + if self_conn: + conn.commit() + conn.close() - Raises: - DatabaseError: + return updated - Returns: - tuple or dict: updated tuple - """ - updated = () - self_conn = False - set_clause, values = postsqldb.updateStringFactory(payload['update']) - values.append(payload['id']) - sql = f"UPDATE {site}_item_info SET {set_clause} WHERE id=%s RETURNING *;" - try: - if not conn: - database_config = config.config() - conn = psycopg2.connect(**database_config) - conn.autocommit = False - self_conn = True - - with conn.cursor() as cur: - cur.execute(sql, values) - rows = cur.fetchone() - if rows and convert: - updated = postsqldb.tupleDictionaryFactory(cur.description, rows) - elif rows and not convert: - updated = rows - - if self_conn: - conn.commit() - conn.close() - - return updated - - except Exception as error: - raise postsqldb.DatabaseError(error, payload, sql) + except Exception as error: + raise postsqldb.DatabaseError(error, payload, sql) def postUpdateItemLocation(site: str, payload: tuple, conn=None): @@ -968,11 +778,7 @@ def postUpdateItemLocation(site: str, payload: tuple, conn=None): # TODO: This should be in the item's process module def postUpdateItem(site:str, payload:dict): - """ POST and update to an item - - Args: - site (str): name of the site the item exists in. - payload (dict): STRICT FORMAT + """ payload (dict): STRICT FORMAT {id: item_id, data: SEE BELOW, user_id: updater} data is complex structure @@ -1060,14 +866,10 @@ def postUpdateItem(site:str, payload:dict): postAddTransaction(conn, site, trans.payload()) except Exception as error: raise postsqldb.DatabaseError(error, payload, "MULTICALL!") - -def postUpdateItemLink(site: str, payload: dict): - """ POST update to ItemLink - Args: - site (str): _description_ - payload (dict): {id, update, old_conv_factor, user_id} - """ +# TODO: This should be in the item's process module +def postUpdateItemLink(site: str, payload: dict): + """ payload (dict): {id, update, old_conv_factor, user_id} """ def postUpdateData(conn, table, payload, convert=True): updated = () set_clause, values = postsqldb.updateStringFactory(payload['update']) @@ -1123,21 +925,7 @@ def postUpdateItemLink(site: str, payload: dict): postAddTransaction(conn, site, transaction.payload()) def postUpdateCostLayer(site, payload, convert=True, conn=None): - """_summary_ - - Args: - conn (_T_connector@connect): Postgresql Connector - site (str): - table (str): - payload (dict): {'id': row_id, 'update': {... column_to_update: value_to_update_to...}} - convert (bool, optional): determines if to return tuple as dictionary. Defaults to False. - - Raises: - DatabaseError: - - Returns: - tuple or dict: updated tuple - """ + """ payload (dict): {'id': row_id, 'update': {... column_to_update: value_to_update_to...}} """ updated = () self_conn = False @@ -1168,47 +956,34 @@ def postUpdateCostLayer(site, payload, convert=True, conn=None): raise postsqldb.DatabaseError(error, payload, sql) def postAddTransaction(site, payload, convert=False, conn=None): - transaction = () - self_conn = False + transaction = () + self_conn = False - with open(f"application/items/sql/insertTransactionsTuple.sql", "r+") as file: - sql = file.read().replace("%%site_name%%", site) - try: - if not conn: - database_config = config.config() - conn = psycopg2.connect(**database_config) - conn.autocommit = False - self_conn = True - with conn.cursor() as cur: - cur.execute(sql, payload) - rows = cur.fetchone() - if rows and convert: - transaction = postsqldb.tupleDictionaryFactory(cur.description, rows) - elif rows and not convert: - transaction = rows - if self_conn: - conn.commit() - conn.close() - - return transaction - except Exception as error: - raise postsqldb.DatabaseError(error, payload, sql) + with open(f"application/items/sql/insertTransactionsTuple.sql", "r+") as file: + sql = file.read().replace("%%site_name%%", site) + try: + if not conn: + database_config = config.config() + conn = psycopg2.connect(**database_config) + conn.autocommit = False + self_conn = True + with conn.cursor() as cur: + cur.execute(sql, payload) + rows = cur.fetchone() + if rows and convert: + transaction = postsqldb.tupleDictionaryFactory(cur.description, rows) + elif rows and not convert: + transaction = rows + if self_conn: + conn.commit() + conn.close() + + return transaction + except Exception as error: + raise postsqldb.DatabaseError(error, payload, sql) def postInsertItemLink(site, payload, convert=True, conn=None): - """insert payload into itemlinks table of site - - Args: - conn (_T_connector@connect): Postgresql Connector - site (str): - payload (tuple): (barcode[str], link[int], data[jsonb], conv_factor[float]) - convert (bool, optional): Determines if to return tuple as dictionary. Defaults to False. - - Raises: - DatabaseError: - - Returns: - tuple or dict: inserted tuple - """ + """ payload (tuple): (barcode[str], link[int], data[jsonb], conv_factor[float]) """ link = () self_conn = False @@ -1238,21 +1013,7 @@ def postInsertItemLink(site, payload, convert=True, conn=None): raise postsqldb.DatabaseError(error, payload, sql) def postUpdateItemByID(site, payload, convert=True, conn=None): - """ high level update of an item specific data, none of its relationships - - Args: - conn (_T_connector@connect): Postgresql Connector - site (str): - table (str): - payload (dict): {'id': row_id, 'update': {... column_to_update: value_to_update_to...}} - convert (bool, optional): determines if to return tuple as dictionary. Defaults to False. - - Raises: - DatabaseError: - - Returns: - tuple or dict: updated tuple - """ + """ payload (dict): {'id': row_id, 'update': {... column_to_update: value_to_update_to...}} """ updated = () self_conn = False set_clause, values = postsqldb.updateStringFactory(payload['update']) diff --git a/application/items/items_API.py b/application/items/items_API.py index d462d91..d5aa8f5 100644 --- a/application/items/items_API.py +++ b/application/items/items_API.py @@ -7,7 +7,7 @@ import math # APPLICATION IMPORTS from config import config -from user_api import login_required +from application.access_module import access_api import application.postsqldb as db from application.items import database_items from application.items import items_processes @@ -15,15 +15,15 @@ import application.database_payloads as dbPayloads items_api = Blueprint('items_api', __name__, template_folder="templates", static_folder="static") - def update_session_user(): - database_config = config() - with psycopg2.connect(**database_config) as conn: - user = db.LoginsTable.get_washed_tuple(conn, (session['user_id'],)) - session['user'] = user + database_config = config() + with psycopg2.connect(**database_config) as conn: + user = db.LoginsTable.get_washed_tuple(conn, (session['user_id'],)) + session['user'] = user +# ROOT TEMPLATE ROUTES @items_api.route("/") -@login_required +@access_api.login_required def items(): update_session_user() sites = [site[1] for site in db.get_sites(session['user']['sites'])] @@ -32,7 +32,7 @@ def items(): sites=sites) @items_api.route("/") -@login_required +@access_api.login_required def item(id): sites = [site[1] for site in db.get_sites(session['user']['sites'])] database_config = config() @@ -41,47 +41,30 @@ def item(id): return render_template("item_new.html", id=id, units=units, current_site=session['selected_site'], sites=sites) @items_api.route("/transaction") -@login_required +@access_api.login_required def transaction(): - sites = [site[1] for site in db.get_sites(session['user']['sites'])] - database_config = config() - with psycopg2.connect(**database_config) as conn: - units = db.UnitsTable.getAll(conn) - return render_template("transaction.html", units=units, current_site=session['selected_site'], sites=sites, proto={'referrer': request.referrer}) + sites = [site[1] for site in db.get_sites(session['user']['sites'])] + database_config = config() + with psycopg2.connect(**database_config) as conn: + units = db.UnitsTable.getAll(conn) + return render_template("transaction.html", units=units, current_site=session['selected_site'], sites=sites, proto={'referrer': request.referrer}) @items_api.route("/transactions/") -@login_required +@access_api.login_required def transactions(id): - """This is the main endpoint to reach the webpage for an items transaction history - --- - parameters: - - name: id - in: path - type: integer - required: true - default: all - responses: - 200: - description: Returns the transactions.html webpage for the item with passed ID - """ - sites = [site[1] for site in db.get_sites(session['user']['sites'])] - return render_template("transactions.html", id=id, current_site=session['selected_site'], sites=sites) + sites = [site[1] for site in db.get_sites(session['user']['sites'])] + return render_template("transactions.html", id=id, current_site=session['selected_site'], sites=sites) @items_api.route("//itemLink/") -@login_required +@access_api.login_required def itemLink(parent_id, id): - sites = [site[1] for site in db.get_sites(session['user']['sites'])] - return render_template("itemlink.html", current_site=session['selected_site'], sites=sites, proto={'referrer': request.referrer}, id=id) + sites = [site[1] for site in db.get_sites(session['user']['sites'])] + return render_template("itemlink.html", current_site=session['selected_site'], sites=sites, proto={'referrer': request.referrer}, id=id) +# API CALLS @items_api.route("/getTransactions", methods=["GET"]) -@login_required +@access_api.login_required def getTransactions(): - """ GET a subquery of transactions by passing a logistics_info_id, limit, and page - --- - responses: - 200: - description: transactions received successfully. - """ if request.method == "GET": recordset = [] count = 0 @@ -95,23 +78,8 @@ def getTransactions(): return jsonify({"transactions": recordset, "end": math.ceil(count/limit), "error": True, "message": f"method {request.method} is not allowed."}) @items_api.route("/getTransaction", methods=["GET"]) -@login_required +@access_api.login_required def getTransaction(): - """ GET a transaction from the system by passing an ID - --- - parameters: - - in: query - name: id - schema: - type: integer - minimum: 1 - default: 1 - required: true - description: The transaction.id - responses: - 200: - description: Transaction Object received successfully. - """ transaction = () if request.method == "GET": id = int(request.args.get('id', 1)) @@ -121,22 +89,8 @@ def getTransaction(): return jsonify({"transaction": transaction, "error": True, "message": f"method {request.method} is not allowed."}) @items_api.route("/getItem", methods=["GET"]) -@login_required +@access_api.login_required def get_item(): - """ GET item from system by passing its ID - --- - parameters: - - in: query - name: id - schema: - type: integer - minimum: 1 - default: 1 - description: item.id - responses: - 200: - description: Item.id received successfully! - """ if request.method == "GET": id = int(request.args.get('id', 1)) site_name = session['selected_site'] @@ -146,46 +100,8 @@ def get_item(): return jsonify({'item': item, 'error': True, 'message': f'method {request.method} not allowed.'}) @items_api.route("/getItemsWithQOH", methods=['GET']) -@login_required +@access_api.login_required def pagninate_items(): - """ GET items from the system by passing a page, limit, search_string, sort, and order - --- - parameters: - - in: query - name: page - schema: - type: integer - default: 1 - description: page number for offset - - in: query - name: limit - schema: - type: integer - default: 50 - description: number of records to grab - - in: query - name: search_string - schema: - type: string - default: '' - description: string to look for in column search_string - - in: query - name: sort - schema: - type: string - default: '' - description: items table column to sort by - - in: query - name: order - schema: - type: string - enum: ['ASC', 'DESC'] - default: 'ASC' - description: Order to sort items table sort parameter by - responses: - 200: - description: Items received successfully. - """ items = [] count = 0 if request.method == "GET": @@ -207,33 +123,8 @@ def pagninate_items(): return jsonify({'items': items, "end": math.ceil(count/limit), 'error':True, 'message': 'There was a problem loading the items!'}) @items_api.route('/getModalItems', methods=["GET"]) -@login_required +@access_api.login_required def getModalItems(): - """ GET items from the system by passing a page, limit, search_string. For select modals - --- - parameters: - - in: query - name: page - schema: - type: integer - default: 1 - description: page number for offset - - in: query - name: limit - schema: - type: integer - default: 25 - description: number of records to grab - - in: query - name: search_string - schema: - type: string - default: '' - description: string to look for in column search_string - responses: - 200: - description: Items received successfully. - """ recordset, count = tuple(), 0 if request.method == "GET": page = int(request.args.get('page', 1)) @@ -247,29 +138,8 @@ def getModalItems(): return jsonify({"items":recordset, "end":math.ceil(count/limit), "error":True, "message": f"method {request.method} is not allowed."}) @items_api.route('/getPrefixes', methods=["GET"]) -@login_required +@access_api.login_required def getModalPrefixes(): - """ GET prefixes from the system by passing page and limit. - --- - parameters: - - in: query - name: page - schema: - type: integer - minimum: 1 - default: 1 - description: page of the database records - - in: query - name: limit - schema: - type: integer - minimum: 1 - default: 10 - description: number of database records to GET - responses: - 200: - description: Prefixes received from the system successfully! - """ recordset = [] count = 0 if request.method == "GET": @@ -283,36 +153,8 @@ def getModalPrefixes(): return jsonify({"prefixes":recordset, "end":math.ceil(count/limit), "error":True, "message":f"method {request.method} is not allowed!"}) @items_api.route('/getZonesBySku', methods=["GET"]) -@login_required +@access_api.login_required def getZonesbySku(): - """ GET zones by sku by passing page, limit, item_id - --- - parameters: - - in: query - name: page - schema: - type: integer - minimum: 1 - default: 1 - description: page of the records to GET - - in: query - name: limit - schema: - type: integer - minimum: 1 - default: 10 - description: number of records to grab from the system - - in: query - name: item_id - schema: - type: integer - minimum: 1 - default: 1 - description: item_id to pull zones for - responses: - 200: - description: Zones received successfully. - """ zones, count = [], 0 if request.method == "GET": page = int(request.args.get('page', 1)) @@ -325,43 +167,8 @@ def getZonesbySku(): return jsonify({'zones': zones, 'endpage': math.ceil(count/limit), 'error':False, 'message': f'method {request.method} not allowed.'}) @items_api.route('/getLocationsBySkuZone', methods=['GET']) -@login_required +@access_api.login_required def getLocationsBySkuZone(): - """ GET locations by sku by passing page, limit, item_id, zone_id - --- - parameters: - - in: query - name: page - schema: - type: integer - minimum: 1 - default: 1 - description: page of the records to GET - - in: query - name: limit - schema: - type: integer - minimum: 1 - default: 10 - description: number of records to grab from the system - - in: query - name: item_id - schema: - type: integer - minimum: 1 - default: 1 - description: item_id to pull locations for zone_id - - in: query - name: zone_id - schema: - type: integer - minimum: 1 - default: 1 - description: zone_id to pull locations for item_id - responses: - 200: - description: Locations received successfully. - """ locations, count = [], 0 if request.method == "GET": zone_id = int(request.args.get('zone_id', 1)) @@ -375,29 +182,8 @@ def getLocationsBySkuZone(): return jsonify({'locations': locations, 'endpage': math.ceil(count/limit), 'error': True, 'message': f'method {request.method} is not allowed.'}) @items_api.route('/getBrands', methods=['GET']) -@login_required +@access_api.login_required def getBrands(): - """ GET brands from the system by passing page, limit - --- - parameters: - - in: query - name: page - schema: - type: integer - minimum: 1 - default: 1 - description: page of the records to GET - - in: query - name: limit - schema: - type: integer - minimum: 1 - default: 10 - description: number of records to grab from the system - responses: - 200: - description: Brands received successfully. - """ brands, count = [], 0 if request.method == "GET": page = int(request.args.get('page', 1)) @@ -409,25 +195,8 @@ def getBrands(): return jsonify({'brands': brands, 'endpage': math.ceil(count/limit), 'error': True, 'message': f'method {request.method} is not allowed.'}) @items_api.route('/updateItem', methods=['POST']) -@login_required +@access_api.login_required def updateItem(): - """ POST update to item in the system by passing item_id, data - --- - parameters: - - in: query - name: item_id - schema: - type: integer - minimum: 1 - default: 1 - description: item_id that the POST targets - - in: header - name: data - description: data to update in system - responses: - 200: - description: item updated successfully. - """ if request.method == "POST": id = request.get_json()['id'] data = request.get_json()['data'] @@ -437,41 +206,8 @@ def updateItem(): return jsonify({'error': True, 'message': f'method {request.method} is not allowed!'}) @items_api.route('/updateItemLink', methods=['POST']) -@login_required +@access_api.login_required def updateItemLink(): - """ UPDATE item link by passing id, conv_factor, barcode, old_conv - --- - parameters: - - in: query - name: id - schema: - type: integer - minimum: 1 - default: 1 - required: true - description: Id of item link to update - - in: query - name: conv_factor - schema: - type: integer - required: true - description: new conversion factor of item_link id - - in: query - name: barcode - schema: - type: string - required: true - description: barcode of item_link id - - in: query - name: old_conv - schema: - type: integer - required: true - description: old conversion factor of item_link id - responses: - 200: - description: Item Link updated successfully. - """ if request.method == "POST": id = request.get_json()['id'] conv_factor = request.get_json()['conv_factor'] @@ -484,29 +220,8 @@ def updateItemLink(): return jsonify({'error': True, 'message': f"method {request.method} not allowed."}) @items_api.route('/getPossibleLocations', methods=["GET"]) -@login_required +@access_api.login_required def getPossibleLocations(): - """ GET locations with zones by passing a page and limit - --- - parameters: - - in: query - name: page - schema: - type: interger - minimum: 1 - default: 1 - description: page in the records to GET - - in: query - name: limit - schema: - type: interger - minimum: 1 - default: 1 - description: number of records to GET - responses: - 200: - description: Locations GET successful. - """ locations, count = (), 0 if request.method == "GET": page = int(request.args.get('page', 1)) @@ -518,22 +233,8 @@ def getPossibleLocations(): return jsonify({'locations': locations, 'end':math.ceil(count/limit), 'error':True, 'message': f'method {request.method} not allowed.'}) @items_api.route('/getLinkedItem', methods=["GET"]) -@login_required +@access_api.login_required def getLinkedItem(): - """ GET itemlink from system by passing an ID - --- - parameters: - - in: query - name: id - schema: - type: integer - default: 1 - required: true - description: item link to get from the system - responses: - 200: - description: Item Link GET successful. - """ linked_item = {} if request.method == "GET": id = int(request.args.get('id', 1)) @@ -543,36 +244,8 @@ def getLinkedItem(): return jsonify({'linked_item': linked_item, 'error': True, 'message': f'method {request.method} not allowed'}) @items_api.route('/addLinkedItem', methods=["POST"]) -@login_required +@access_api.login_required def addLinkedItem(): - """ POST a link between items by passing a parent_id, a child_id, conv_factor - --- - parameters: - - in: query - name: parent_id - schema: - type: integer - default: 1 - required: true - description: id to linked list item - - in: query - name: child_id - schema: - type: integer - default: 1 - required: true - description: id to item to be linked to list. - - in: query - name: conv_factor - schema: - type: integer - default: 1 - required: true - description: integer factor between child id to parent id. - responses: - 200: - description: Items linked successfully. - """ if request.method == "POST": parent_id = request.get_json()['parent_id'] child_id = request.get_json()['child_id'] @@ -591,35 +264,8 @@ def addLinkedItem(): return jsonify({'error': True, 'message': 'These was an error with adding to the linked list!'}) @items_api.route('/addBlankItem', methods=["POST"]) +@access_api.login_required def addBlankItem(): - """ POST new Blank item to the system given a barcode, item_name, subtype - --- - parameters: - - in: query - name: barcode - schema: - type: string - default: 1 - required: true - description: barcode for the item - - in: query - name: item_name - schema: - type: string - default: 1 - required: true - description: name of the blank item - - in: query - name: subtype - schema: - type: string - default: 1 - required: true - description: type of item this is categorized to be. - responses: - 200: - description: Item added successfully. - """ if request.method == "POST": data = { 'barcode': request.get_json()['barcode'], @@ -635,35 +281,8 @@ def addBlankItem(): return jsonify({'error': True, 'message': 'These was an error with adding Item!'}) @items_api.route('/addSKUPrefix', methods=["POST"]) +@access_api.login_required def addSKUPrefix(): - """ POST new SKU Prefix to the system given a uuid, name, description - --- - parameters: - - in: query - name: uuid - schema: - type: string - default: 1 - required: true - description: uuid for the sku which will be attached to items - - in: query - name: name - schema: - type: string - default: 1 - required: true - description: name of the Prefix - - in: query - name: description - schema: - type: string - default: 1 - required: true - description: description of the Prefix. - responses: - 200: - description: Prefix added successfully. - """ if request.method == "POST": site_name = session['selected_site'] prefix = dbPayloads.SKUPrefixPayload( @@ -676,35 +295,9 @@ def addSKUPrefix(): return jsonify({'error': True, 'message': 'These was an error with adding this Prefix!'}) @items_api.route('/addConversion', methods=['POST']) +@access_api.login_required def addConversion(): - """ POST new conversion to the system given a item_id, uom_id, conv_factor - --- - parameters: - - in: header - name: item_id - schema: - type: integer - default: 1 - required: true - description: item_id the conversion applies to - - in: header - name: uom_id - schema: - type: integer - default: 1 - required: true - description: uom_id to match item_id uom to convert to - - in: header - name: conv_factor - schema: - type: float - default: 1 - required: true - description: item_id.uom -> uom_id amount - responses: - 200: - description: Prefix added successfully. - """ + if request.method == "POST": item_id = request.get_json()['parent_id'] uom_id = request.get_json()['uom_id'] @@ -721,21 +314,8 @@ def addConversion(): return jsonify(error=True, message="Unable to save this conversion, ERROR!") @items_api.route('/deleteConversion', methods=['POST']) +@access_api.login_required def deleteConversion(): - """ POST delete conversion to the system given a conversion_id - --- - parameters: - - in: header - name: conversion_id - schema: - type: integer - default: 1 - required: true - description: conversion_id to be deleted - responses: - 200: - description: Prefix added successfully. - """ if request.method == "POST": conversion_id = request.get_json()['conversion_id'] site_name = session['selected_site'] @@ -744,28 +324,8 @@ def deleteConversion(): return jsonify(error=True, message="Unable to delete this conversion, ERROR!") @items_api.route('/updateConversion', methods=['POST']) +@access_api.login_required def updateConversion(): - """ POST update conversion to the system given a conversion_id, update dictionary - --- - parameters: - - in: header - name: conversion_id - schema: - type: integer - default: 1 - required: true - description: conversion_id to be deleted - - in: header - name: update - schema: - type: dict - default: 1 - required: true - description: data to update in key=column, value=update_data - responses: - 200: - description: conversion updated successfully. - """ if request.method == "POST": conversion_id = request.get_json()['conversion_id'] update_dictionary = request.get_json()['update'] @@ -775,28 +335,8 @@ def updateConversion(): return jsonify(error=True, message="Unable to save this conversion, ERROR!") @items_api.route('/addPrefix', methods=['POST']) +@access_api.login_required def addPrefix(): - """ POST add prefix to the system given a item_info_id and prefix_id - --- - parameters: - - in: header - name: item_info_id - schema: - type: integer - default: 1 - required: true - description: item_info_id to be updated - - in: header - name: prefix_id - schema: - type: integer - default: 1 - required: true - description: prefix_id to be added - responses: - 200: - description: conversion updated successfully. - """ if request.method == "POST": item_info_id = request.get_json()['parent_id'] prefix_id = request.get_json()['prefix_id'] @@ -808,28 +348,8 @@ def addPrefix(): return jsonify(error=True, message="Unable to save this prefix, ERROR!") @items_api.route('/deletePrefix', methods=['POST']) +@access_api.login_required def deletePrefix(): - """ POST delete prefix from the system given a item_info_id and prefix_id - --- - parameters: - - in: header - name: item_info_id - schema: - type: integer - default: 1 - required: true - description: item_info_id to be updated - - in: header - name: prefix_id - schema: - type: integer - default: 1 - required: true - description: prefix_id to be added - responses: - 200: - description: conversion updated successfully. - """ if request.method == "POST": item_info_id = request.get_json()['item_info_id'] prefix_id = request.get_json()['prefix_id'] @@ -841,21 +361,8 @@ def deletePrefix(): return jsonify(error=True, message="Unable to delete this prefix, ERROR!") @items_api.route('/refreshSearchString', methods=['POST']) +@access_api.login_required def refreshSearchString(): - """ POST update search_string to the system given a item_id - --- - parameters: - - in: header - name: item_info_id - schema: - type: integer - default: 1 - required: true - description: item_id to be updated - responses: - 200: - description: conversion updated successfully. - """ if request.method == "POST": item_id = request.get_json()['item_id'] site_name = session['selected_site'] @@ -866,28 +373,8 @@ def refreshSearchString(): return jsonify(error=True, message="Unable to update this search string, ERROR!") @items_api.route('/postNewItemLocation', methods=['POST']) +@access_api.login_required def postNewItemLocation(): - """ POST add itemlocation to the system given a item_id and location_id - --- - parameters: - - in: header - name: item_id - schema: - type: integer - default: 1 - required: true - description: item_id to be attached location_id to - - in: header - name: item_id - schema: - type: integer - default: 1 - required: true - description: location_id to attach item_id to - responses: - 200: - description: conversion updated successfully. - """ if request.method == "POST": item_id = request.get_json()['item_id'] location_id = request.get_json()['location_id'] @@ -898,6 +385,7 @@ def postNewItemLocation(): return jsonify(error=True, message="Unable to save this location, ERROR!") @items_api.route("/getItemLocations", methods=["GET"]) +@access_api.login_required def getItemLocations(): recordset = [] count = 0 @@ -911,8 +399,8 @@ def getItemLocations(): return jsonify({"locations":recordset, "end":math.ceil(count/limit), "error":False, "message":"item fetched succesfully!"}) return jsonify({"locations":recordset, "end": math.ceil(count/limit), "error":True, "message":"There was an error with this GET statement"}) - @items_api.route('/postTransaction', methods=["POST"]) +@access_api.login_required def post_transaction(): if request.method == "POST": result = items_processes.postAdjustment( diff --git a/application/items/items_processes.py b/application/items/items_processes.py index 17157aa..4a66a30 100644 --- a/application/items/items_processes.py +++ b/application/items/items_processes.py @@ -1,9 +1,9 @@ -# 3rd party imports +# 3RD PARTY IMPORTS import datetime import psycopg2 import json -# applications imports +# APPLICATION IMPORTS from application.items import database_items import application.postsqldb as db import application.database_payloads as dbPayloads diff --git a/application/poe/poe_api.py b/application/poe/poe_api.py index 02c8846..a11eab5 100644 --- a/application/poe/poe_api.py +++ b/application/poe/poe_api.py @@ -1,12 +1,12 @@ -# 3rd Party imports +# 3RD PARTY IMPORTS from flask import ( Blueprint, request, render_template, redirect, session, url_for, send_file, jsonify, Response ) import psycopg2 -# applications imports +# APPLICATION IMPORTS from config import config -from user_api import login_required +from application.access_module import access_api from application.poe import poe_processes, poe_database from application import postsqldb @@ -15,14 +15,14 @@ point_of_ease = Blueprint('poe', __name__, template_folder="templates", static_f @point_of_ease.route('/scanner', methods=["GET"]) -@login_required +@access_api.login_required def scannerEndpoint(): sites = [site[1] for site in postsqldb.get_sites(session['user']['sites'])] return render_template('scanner.html', current_site=session['selected_site'], sites=sites) @point_of_ease.route('/receipts', methods=["GET"]) -@login_required +@access_api.login_required def receiptsEndpoint(): sites = [site[1] for site in postsqldb.get_sites(session['user']['sites'])] database_config = config() @@ -32,7 +32,7 @@ def receiptsEndpoint(): sites=sites, units=units) @point_of_ease.route('/getItem/barcode', methods=["GET"]) -@login_required +@access_api.login_required def getItemBarcode(): record = {} if request.method == "GET": @@ -46,9 +46,8 @@ def getItemBarcode(): return jsonify({"item":record, "error":False, "message":"item fetched succesfully!"}) return jsonify({"item":record, "error":True, "message":"There was an error with this GET statement"}) - @point_of_ease.route('/postTransaction', methods=["POST"]) -@login_required +@access_api.login_required def post_transaction(): if request.method == "POST": result = poe_processes.postTransaction( @@ -59,9 +58,8 @@ def post_transaction(): return jsonify(result) return jsonify({"error":True, "message":"There was an error with this POST statement"}) - @point_of_ease.route('/postReceipt', methods=["POST"]) -@login_required +@access_api.login_required def post_receipt(): if request.method == "POST": site_name = session['selected_site'] diff --git a/application/poe/poe_database.py b/application/poe/poe_database.py index 48ead3a..6d69a23 100644 --- a/application/poe/poe_database.py +++ b/application/poe/poe_database.py @@ -1,4 +1,7 @@ +# 3RD PARTY IMPORTS import psycopg2 + +# APPLICATION IMPORTS import config from application import postsqldb @@ -210,8 +213,7 @@ def selectItemAllByBarcode(site, payload, convert=True, conn=None): return item except (Exception, psycopg2.DatabaseError) as error: raise postsqldb.DatabaseError(error, payload, getItemAllByBarcode_sql) - - + def insertCostLayersTuple(site, payload, convert=True, conn=None): cost_layer = () self_conn = False @@ -389,7 +391,6 @@ def updateItemLocation(site, payload, convert=True, conn=None): except Exception as error: return error - def deleteCostLayersTuple(site, payload, convert=True, conn=None): deleted = () self_conn = False diff --git a/application/poe/poe_processes.py b/application/poe/poe_processes.py index a8510f0..6d187f6 100644 --- a/application/poe/poe_processes.py +++ b/application/poe/poe_processes.py @@ -1,8 +1,8 @@ -# 3rd Party imports +# 3RD PARTY IMPORTS import datetime import psycopg2 -# applications imports +# APPLICATION IMPORTS from application import postsqldb, database_payloads from application.poe import poe_database import config @@ -12,9 +12,8 @@ point of ease module. """ def postTransaction(site_name, user_id, data: dict, conn=None): - '''Takes a set of data as a dictionary and inserts them into the system for passed site_name. ''' - #dict_keys(['item_id', 'logistics_info_id', 'barcode', 'item_name', 'transaction_type', - # 'quantity', 'description', 'cost', 'vendor', 'expires', 'location_id']) + """ dict_keys(['item_id', 'logistics_info_id', 'barcode', 'item_name', 'transaction_type', + 'quantity', 'description', 'cost', 'vendor', 'expires', 'location_id'])""" def quantityFactory(quantity_on_hand:float, quantity:float, transaction_type:str): if transaction_type == "Adjust In": quantity_on_hand += quantity @@ -101,8 +100,6 @@ def postTransaction(site_name, user_id, data: dict, conn=None): return {"error": False, "message":f"Transaction Successful!"} def post_receipt(site_name, user_id, data: dict, conn=None): - '''Takes a list of items and opens and creates a SIR (SCANNED IN RECEIPT) into the system with the items linked - to said receipt.''' # data = {'items': items} self_conn = False items = data['items'] diff --git a/application/receipts/receipts_api.py b/application/receipts/receipts_api.py index 0632a2e..b74b30a 100644 --- a/application/receipts/receipts_api.py +++ b/application/receipts/receipts_api.py @@ -1,5 +1,7 @@ # 3RD PARTY IMPORTS -from flask import (Blueprint, request, render_template, session, jsonify, current_app, send_from_directory) +from flask import ( + Blueprint, request, render_template, session, jsonify, current_app, send_from_directory + ) import math import postsqldb import mimetypes @@ -7,7 +9,7 @@ import os # APPLICATION IMPORTS import webpush -from user_api import login_required +from application.access_module import access_api from application import postsqldb, database_payloads from application.receipts import receipts_processes, receipts_database @@ -17,13 +19,13 @@ receipt_api = Blueprint('receipt_api', __name__, template_folder='templates', st # ROOT TEMPLATE ROUTES @receipt_api.route("/") -@login_required +@access_api.login_required def receipts(): sites = [site[1] for site in postsqldb.get_sites(session['user']['sites'])] return render_template("receipts_index.html", current_site=session['selected_site'], sites=sites) @receipt_api.route("/") -@login_required +@access_api.login_required def receipt(id): sites = [site[1] for site in postsqldb.get_sites(session['user']['sites'])] units = postsqldb.get_units_of_measure() @@ -31,8 +33,8 @@ def receipt(id): # API ROUTES -# Added to Database @receipt_api.route('/api/getItems', methods=["GET"]) +@access_api.login_required def getItems(): recordset = [] count = {'count': 0} @@ -47,8 +49,8 @@ def getItems(): return jsonify({"items":recordset, "end":math.ceil(count['count']/limit), "error":False, "message":"items fetched succesfully!"}) return jsonify({"items":recordset, "end":math.ceil(count['count']/limit), "error":True, "message":"There was an error with this GET statement"}) -# Added to Database @receipt_api.route('/api/getVendors', methods=["GET"]) +@access_api.login_required def getVendors(): recordset = [] count = 0 @@ -61,8 +63,8 @@ def getVendors(): return jsonify({"vendors":recordset, "end":math.ceil(count/limit), "error":False, "message":"items fetched succesfully!"}) return jsonify({"vendors":recordset, "end":math.ceil(count/limit), "error":True, "message":"There was an error with this GET statement"}) -# Added to Database @receipt_api.route('/api/getLinkedLists', methods=["GET"]) +@access_api.login_required def getLinkedLists(): recordset = [] count = 0 @@ -75,8 +77,8 @@ def getLinkedLists(): return jsonify({"items":recordset, "end":math.ceil(count/limit), "error":False, "message":"items fetched succesfully!"}) return jsonify({"items":recordset, "end":math.ceil(count/limit), "error":True, "message":"There was an error with this GET statement"}) -# Added to database @receipt_api.route('/api/getReceipts', methods=["GET"]) +@access_api.login_required def getReceipts(): recordset = [] if request.method == "GET": @@ -88,8 +90,8 @@ def getReceipts(): return jsonify({'receipts':recordset, "end": math.ceil(count/limit), 'error': False, "message": "Get Receipts Successful!"}) return jsonify({'receipts': recordset, "end": math.ceil(count/limit), 'error': True, "message": "Something went wrong while getting receipts!"}) -# Added to database @receipt_api.route('/api/getReceipt', methods=["GET"]) +@access_api.login_required def getReceipt(): receipt = [] if request.method == "GET": @@ -99,8 +101,8 @@ def getReceipt(): return jsonify({'receipt': receipt, 'error': False, "message": "Get Receipts Successful!"}) return jsonify({'receipt': receipt, 'error': True, "message": "Something went wrong while getting receipts!"}) -# added to database @receipt_api.route('/api/addReceipt', methods=["POST", "GET"]) +@access_api.login_required def addReceipt(): if request.method == "GET": user_id = session['user_id'] @@ -113,8 +115,8 @@ def addReceipt(): return jsonify({'error': False, "message": "Receipt Added Successful!"}) return jsonify({'error': True, "message": "Something went wrong while adding receipt!"}) -# Added to Database @receipt_api.route('/api/addSKULine', methods=["POST"]) +@access_api.login_required def addSKULine(): if request.method == "POST": item_id = int(request.get_json()['item_id']) @@ -139,8 +141,8 @@ def addSKULine(): return jsonify({'error': False, "message": "Line added Succesfully"}) return jsonify({'error': True, "message": "Something went wrong while add SKU line!"}) -# Added to Database @receipt_api.route('/api/deleteLine', methods=["POST"]) +@access_api.login_required def deleteLine(): if request.method == "POST": line_id = int(request.get_json()['line_id']) @@ -149,8 +151,8 @@ def deleteLine(): return jsonify({'error': False, "message": "Line Deleted Succesfully"}) return jsonify({'error': True, "message": "Something went wrong while deleting line!"}) -# Added to Database @receipt_api.route('/api/denyLine', methods=["POST"]) +@access_api.login_required def denyLine(): if request.method == "POST": line_id = int(request.get_json()['line_id']) @@ -159,8 +161,8 @@ def denyLine(): return jsonify({'error': False, "message": "Line Denied Succesfully"}) return jsonify({'error': True, "message": "Something went wrong while denying line!"}) -# Added to database @receipt_api.route('/api/saveLine', methods=["POST"]) +@access_api.login_required def saveLine(): if request.method == "POST": line_id = int(request.get_json()['line_id']) @@ -173,8 +175,8 @@ def saveLine(): return jsonify({'error': False, "message": "Line Saved Succesfully"}) return jsonify({'error': True, "message": "Something went wrong while saving line!"}) -# Added to Process and database! @receipt_api.route('/api/postLinkedItem', methods=["POST"]) +@access_api.login_required def postLinkedItem(): if request.method == "POST": receipt_item_id = int(request.get_json()['receipt_item_id']) @@ -194,8 +196,9 @@ def postLinkedItem(): return jsonify({'error': False, "message": "Line Saved Succesfully"}) return jsonify({'error': True, "message": "Something went wrong while saving line!"}) -# Added to processes and Database + @receipt_api.route('/api/resolveLine', methods=["POST"]) +@access_api.login_required def resolveLine(): if request.method == "POST": line_id = int(request.get_json()['line_id']) @@ -206,8 +209,8 @@ def resolveLine(): return jsonify({'error': False, "message": "Line Saved Succesfully"}) return jsonify({'error': True, "message": "Something went wrong while saving line!"}) -# add to database @receipt_api.route('/api/postVendorUpdate', methods=["POST"]) +@access_api.login_required def postVendorUpdate(): if request.method == "POST": receipt_id = int(request.get_json()['receipt_id']) @@ -217,8 +220,8 @@ def postVendorUpdate(): return jsonify({'error': False, "message": "Line Saved Succesfully"}) return jsonify({'error': True, "message": "Something went wrong while saving line!"}) -# added to database @receipt_api.route('/api/resolveReceipt', methods=["POST"]) +@access_api.login_required def resolveReceipt(): if request.method == "POST": receipt_id = int(request.get_json()['receipt_id']) @@ -229,8 +232,8 @@ def resolveReceipt(): return jsonify({'error': False, "message": "Line Saved Succesfully"}) return jsonify({'error': True, "message": "Something went wrong while saving line!"}) -# added to database @receipt_api.route('/api/uploadfile/', methods=["POST"]) +@access_api.login_required def uploadFile(receipt_id): file = request.files['file'] file_path = current_app.config['FILES_FOLDER'] + f"/receipts/{file.filename.replace(" ", "_")}" @@ -249,15 +252,15 @@ def uploadFile(receipt_id): receipts_database.updateReceiptsTuple(site_name, {'id': receipt_id, 'update': {'files': receipt_files}}) return jsonify({}) -# Does not need to be added to Database @receipt_api.route('/api/getFile/') +@access_api.login_required def getFile(file_name): path_ = current_app.config['FILES_FOLDER'] + "/receipts" print(path_) return send_from_directory(path_, file_name) -# Added to database @receipt_api.route('/api/checkAPI', methods=["POST"]) +@access_api.login_required def checkAPI(): if request.method == "POST": line_id = int(request.get_json()['line_id']) diff --git a/application/recipes/database_recipes.py b/application/recipes/database_recipes.py index 90ab78c..920822d 100644 --- a/application/recipes/database_recipes.py +++ b/application/recipes/database_recipes.py @@ -1,9 +1,12 @@ -from application import postsqldb -import config +# 3RD PARTY APPLICATIONS import psycopg2 import random import string +# APPLICATION IMPORTS +from application import postsqldb +import config + def getUUID(n): random_string = ''.join(random.choices(string.ascii_letters + string.digits, k=n)) return random_string diff --git a/application/recipes/recipes_api.py b/application/recipes/recipes_api.py index 790b053..936df8b 100644 --- a/application/recipes/recipes_api.py +++ b/application/recipes/recipes_api.py @@ -1,53 +1,29 @@ -# 3rd party imports +# 3RD PARTY IMPORTS from flask import ( Blueprint, request, render_template, session, jsonify, current_app, send_from_directory ) import math -# application imports +# APPLICATION IMPORTS import main -from user_api import login_required import webpush +from application.access_module import access_api from application.recipes import database_recipes from application import postsqldb as db recipes_api = Blueprint('recipes_api', __name__, template_folder="templates", static_folder="static") @recipes_api.route("/") -@login_required +@access_api.login_required def recipes(): - """This is the main endpoint to reach the webpage for a list of all recipes - --- - responses: - 200: - description: returns recipes/index.html with sites, current_site. - """ sites = [site[1] for site in main.get_sites(session['user']['sites'])] return render_template("recipes_index.html", current_site=session['selected_site'], sites=sites) @recipes_api.route("//") -@login_required +@access_api.login_required def recipe(id, mode='view'): - """This is the main endpoint to reach the webpage for a recipe's view or edit mode. - --- - parameters: - - name: mode - in: path - type: string - required: true - default: view - - name: id - in: path - type: integer - required: true - default: all - responses: - 200: - description: Respondes with either the Edit or View webpage for the recipe. - """ - units = database_recipes.getUnits() print("woot") if mode == "edit": @@ -56,14 +32,8 @@ def recipe(id, mode='view'): return render_template("recipe_view.html", recipe_id=id, current_site=session['selected_site']) @recipes_api.route('/getRecipes', methods=["GET"]) -@login_required +@access_api.login_required def getRecipes(): - """ Get a subquery of recipes from the database by passing a page, limit - --- - responses: - 200: - description: limit of rows passed returned to requester - """ recipes = [] count=0 if request.method == "GET": @@ -76,15 +46,8 @@ def getRecipes(): return jsonify({'recipes': recipes, 'end': math.ceil(count/limit), 'error': True, 'message': f'method is not allowed: {request.method}'}) @recipes_api.route('/getRecipe', methods=["GET"]) -@login_required +@access_api.login_required def getRecipe(): - """ Get a query for recipe id from database by passing an id - --- - responses: - 200: - description: id queried successfully! - - """ recipe = {} if request.method == "GET": id = int(request.args.get('id', 1)) @@ -94,14 +57,8 @@ def getRecipe(): return jsonify({'recipe': recipe, 'error': True, 'message': f'method {request.method} not allowed'}) @recipes_api.route('/addRecipe', methods=["POST"]) -@login_required +@access_api.login_required def addRecipe(): - """ post a new recipe into the database by passing a recipe_name and recipe description - --- - responses: - 200: - description: Recipe was added successfully into the system - """ if request.method == "POST": recipe_name = request.get_json()['recipe_name'] recipe_description = request.get_json()['recipe_description'] @@ -118,14 +75,8 @@ def addRecipe(): return jsonify({'recipe': recipe, 'error': True, 'message': f'method {request.method}'}) @recipes_api.route('/getItems', methods=["GET"]) -@login_required +@access_api.login_required def getItems(): - """ Pass along a page, limit, and search strings to get a pagination of items from the system - --- - responses: - 200: - description: Items were returned successfully! - """ recordset = [] count = {'count': 0} if request.method == "GET": @@ -139,17 +90,8 @@ def getItems(): return jsonify({"items":recordset, "end":math.ceil(count/limit), "error":True, "message":"There was an error with this GET statement"}) @recipes_api.route('/postUpdate', methods=["POST"]) -@login_required +@access_api.login_required def postUpdate(): - """ This is an endpoint for updating an RecipeTuple in the sites recipes table - --- - responses: - 200: - description: The time was updated successfully! - - Returns: - dict: returns a dictionary containing the updated recipe object, error status, and a message to post for notifications - """ recipe = {} if request.method == "POST": recipe_id = int(request.get_json()['recipe_id']) @@ -160,14 +102,8 @@ def postUpdate(): return jsonify({'recipe': recipe, 'error': True, 'message': 'Update of Recipe unsuccessful!'}) @recipes_api.route('/postCustomItem', methods=["POST"]) -@login_required +@access_api.login_required def postCustomItem(): - """ post a recipe item to the database by passing a uuid, recipe_id, item_type, item_name, uom, qty, and link - --- - responses: - 200: - description: Recipe Item posted successfully! - """ recipe = {} if request.method == "POST": site_name = session['selected_site'] @@ -187,14 +123,8 @@ def postCustomItem(): return jsonify({'recipe': recipe, 'error': True, 'message': f'method {request.method} not allowed!'}) @recipes_api.route('/postSKUItem', methods=["POST"]) -@login_required +@access_api.login_required def postSKUItem(): - """ post a recipe item to the database by passing a recipe_id and item_id - --- - responses: - 200: - description: recipe item was posted successfully! - """ recipe = {} if request.method == "POST": recipe_id = int(request.get_json()['recipe_id']) @@ -217,20 +147,8 @@ def postSKUItem(): return jsonify({'recipe': recipe, 'error': True, 'message': f'method {request.method} is not allowed!'}) @recipes_api.route('/postImage/', methods=["POST"]) -@login_required +@access_api.login_required def uploadImage(recipe_id): - """ post an image for a recipe into the database and files by passing the recipe_id and picture_path - --- - parameters: - - name: recipe_id - in: path - required: true - schema: - type: integer - responses: - 200: - description: image uploaded succesfully! - """ file = request.files['file'] file_path = current_app.config['UPLOAD_FOLDER'] + f"/recipes/{file.filename.replace(" ", "_")}" file.save(file_path) @@ -239,33 +157,15 @@ def uploadImage(recipe_id): return jsonify({'error': False, 'message': 'Recipe was updated successfully!'}) @recipes_api.route('/getImage/') -@login_required +@access_api.login_required def get_image(recipe_id): - """ get the picture path for a recipe by passing teh recipe id in the path - --- - parameters: - - name: recipe_id - in: path - required: true - schema: - type: integer - responses: - 200: - description: image fetched succesfully! - """ site_name = session['selected_site'] picture_path = database_recipes.getPicturePath(site_name, (recipe_id,)) return send_from_directory('static/pictures/recipes', picture_path) @recipes_api.route('/deleteRecipeItem', methods=["POST"]) -@login_required +@access_api.login_required def deleteRecipeItem(): - """ delete recipe item from database by passing the recipe item ID - --- - responses: - 200: - description: recipe item deleted successfully. - """ recipe = {} if request.method == "POST": id = int(request.get_json()['id']) @@ -276,15 +176,8 @@ def deleteRecipeItem(): return jsonify({'recipe': recipe, 'error': True, 'message': f'method {request.method} is not allowed!'}) @recipes_api.route('/saveRecipeItem', methods=["POST"]) -@login_required +@access_api.login_required def saveRecipeItem(): - """ post an update to a recipe item in the database by passing the recipe item ID and an update - payload. - --- - responses: - 200: - description: recipe item updated successfully. - """ recipe = {} if request.method == "POST": id = int(request.get_json()['id']) diff --git a/application/shoppinglists/shoplist_api.py b/application/shoppinglists/shoplist_api.py index 1d79de7..061656f 100644 --- a/application/shoppinglists/shoplist_api.py +++ b/application/shoppinglists/shoplist_api.py @@ -6,7 +6,7 @@ import math # APPLICATION IMPORTS from application import postsqldb, database_payloads -from user_api import login_required +from application.access_module import access_api from application.shoppinglists import shoplist_database shopping_list_api = Blueprint('shopping_list_API', __name__, template_folder="templates", static_folder="static") @@ -14,13 +14,13 @@ shopping_list_api = Blueprint('shopping_list_API', __name__, template_folder="te # ROOT TEMPLATE ROUTES @shopping_list_api.route("/") -@login_required +@access_api.login_required def shopping_lists(): sites = [site[1] for site in postsqldb.get_sites(session['user']['sites'])] return render_template("lists.html", current_site=session['selected_site'], sites=sites) @shopping_list_api.route("//") -@login_required +@access_api.login_required def shopping_list(mode, id): sites = [site[1] for site in postsqldb.get_sites(session['user']['sites'])] if mode == "view": @@ -29,10 +29,10 @@ def shopping_list(mode, id): return render_template("edit.html", id=id, current_site=session['selected_site'], sites=sites) return redirect("/") - # API CALLS # Added to Database @shopping_list_api.route('/api/addList', methods=["POST"]) +@access_api.login_required def addList(): if request.method == "POST": list_name = request.get_json()['list_name'] @@ -52,6 +52,7 @@ def addList(): # Added to Database @shopping_list_api.route('/api/getLists', methods=["GET"]) +@access_api.login_required def getShoppingLists(): lists = [] if request.method == "GET": @@ -86,6 +87,7 @@ def getShoppingLists(): # Added to Database @shopping_list_api.route('/api/getList', methods=["GET"]) +@access_api.login_required def getShoppingList(): if request.method == "GET": sl_id = int(request.args.get('id', 1)) @@ -95,6 +97,7 @@ def getShoppingList(): # Added to Database @shopping_list_api.route('/api/getListItem', methods=["GET"]) +@access_api.login_required def getShoppingListItem(): list_item = {} if request.method == "GET": @@ -106,6 +109,7 @@ def getShoppingListItem(): # Added to database @shopping_list_api.route('/api/getItems', methods=["GET"]) +@access_api.login_required def getItems(): recordset = [] count = {'count': 0} @@ -123,6 +127,7 @@ def getItems(): # Added to database @shopping_list_api.route('/api/postListItem', methods=["POST"]) +@access_api.login_required def postListItem(): if request.method == "POST": data = request.get_json()['data'] @@ -143,6 +148,7 @@ def postListItem(): # Added to Database @shopping_list_api.route('/api/deleteListItem', methods=["POST"]) +@access_api.login_required def deleteListItem(): if request.method == "POST": sli_id = request.get_json()['sli_id'] @@ -153,6 +159,7 @@ def deleteListItem(): # Added to Database @shopping_list_api.route('/api/saveListItem', methods=["POST"]) +@access_api.login_required def saveListItem(): if request.method == "POST": sli_id = request.get_json()['sli_id'] @@ -164,6 +171,7 @@ def saveListItem(): # Added to Database @shopping_list_api.route('/api/getSKUItemsFull', methods=["GET"]) +@access_api.login_required def getSKUItemsFull(): items = [] count = {'count': 0} diff --git a/application/site_management/site_management_api.py b/application/site_management/site_management_api.py index bdd3848..e06400f 100644 --- a/application/site_management/site_management_api.py +++ b/application/site_management/site_management_api.py @@ -5,15 +5,15 @@ from flask import ( import math # APPLICATION IMPORTS -from user_api import login_required from application import postsqldb, database_payloads +from application.access_module import access_api from application.site_management import site_management_database site_management_api = Blueprint('site_management_api', __name__, template_folder="templates", static_folder="static") # ROOT TEMPLATE ROUTES @site_management_api.route("/") -@login_required +@access_api.login_required def site_management_index(): sites = [site[1] for site in postsqldb.get_sites(session['user']['sites'])] if not session.get('user')['system_admin']: @@ -25,7 +25,7 @@ def site_management_index(): # API CALLS # added to database @site_management_api.route('/api/getZones', methods=['GET']) -@login_required +@access_api.login_required def getZones(): if request.method == "GET": records = [] @@ -40,7 +40,7 @@ def getZones(): # added to database @site_management_api.route('/api/getLocations', methods=['GET']) -@login_required +@access_api.login_required def getLocations(): if request.method == "GET": records = [] @@ -55,7 +55,7 @@ def getLocations(): # added to database @site_management_api.route('/api/getVendors', methods=['GET']) -@login_required +@access_api.login_required def getVendors(): if request.method == "GET": records = [] @@ -70,7 +70,7 @@ def getVendors(): # added to database @site_management_api.route('/api/getBrands', methods=['GET']) -@login_required +@access_api.login_required def getBrands(): if request.method == "GET": records = [] @@ -85,7 +85,7 @@ def getBrands(): # added to database @site_management_api.route('/api/getPrefixes', methods=['GET']) -@login_required +@access_api.login_required def getPrefixes(): if request.method == "GET": records = [] @@ -100,6 +100,7 @@ def getPrefixes(): # added to database @site_management_api.route('/api/postAddZone', methods=["POST"]) +@access_api.login_required def postAddZone(): if request.method == "POST": site_name = session['selected_site'] @@ -110,6 +111,7 @@ def postAddZone(): # added to database @site_management_api.route('/api/postEditZone', methods=["POST"]) +@access_api.login_required def postEditZone(): if request.method == "POST": site_name = session['selected_site'] @@ -120,6 +122,7 @@ def postEditZone(): # added to database @site_management_api.route('/api/postAddLocation', methods=["POST"]) +@access_api.login_required def postAddLocation(): if request.method == "POST": site_name = session['selected_site'] @@ -130,6 +133,7 @@ def postAddLocation(): # added to database @site_management_api.route('/api/postAddVendor', methods=["POST"]) +@access_api.login_required def postAddVendor(): if request.method == "POST": site_name = session['selected_site'] @@ -146,6 +150,7 @@ def postAddVendor(): # added to database @site_management_api.route('/api/postEditVendor', methods=["POST"]) +@access_api.login_required def postEditVendor(): if request.method == "POST": site_name = session['selected_site'] @@ -156,6 +161,7 @@ def postEditVendor(): # added to database @site_management_api.route('/api/postAddBrand', methods=["POST"]) +@access_api.login_required def postAddBrand(): if request.method == "POST": site_name = session['selected_site'] @@ -166,6 +172,7 @@ def postAddBrand(): # added to database @site_management_api.route('/api/postEditBrand', methods=["POST"]) +@access_api.login_required def postEditBrand(): if request.method == "POST": site_name = session['selected_site'] @@ -176,6 +183,7 @@ def postEditBrand(): # added to database @site_management_api.route('/api/postAddPrefix', methods=["POST"]) +@access_api.login_required def postAddPrefix(): if request.method == "POST": site_name = session['selected_site'] @@ -186,6 +194,7 @@ def postAddPrefix(): # added to database @site_management_api.route('/api/postEditPrefix', methods=["POST"]) +@access_api.login_required def postEditPrefix(): if request.method == "POST": site_name = session['selected_site'] diff --git a/outh.py b/outh.py new file mode 100644 index 0000000..a2ded98 --- /dev/null +++ b/outh.py @@ -0,0 +1,4 @@ +from authlib.integrations.flask_client import OAuth + +# Initialize OAuth instance +oauth = OAuth() \ No newline at end of file diff --git a/webserver.py b/webserver.py index e14bb3e..3da1fc5 100644 --- a/webserver.py +++ b/webserver.py @@ -1,10 +1,11 @@ from flask import Flask, render_template, session, request, redirect, jsonify from flask_assets import Environment, Bundle -import config, user_api, psycopg2, main -from user_api import login_required, update_session_user +from authlib.integrations.flask_client import OAuth +import config, psycopg2, main import database from webpush import trigger_push_notifications_for_subscriptions from application.administration import administration_api +from application.access_module import access_api from application.site_management import site_management_api from application.recipes import recipes_api from application.items import items_API @@ -12,9 +13,10 @@ from application.poe import poe_api from application.shoppinglists import shoplist_api from application.receipts import receipts_api from flasgger import Swagger - +from outh import oauth app = Flask(__name__, instance_relative_config=True) +oauth.init_app(app) swagger = Swagger(app) UPLOAD_FOLDER = 'static/pictures' FILES_FOLDER = 'static/files' @@ -22,10 +24,22 @@ app.config.from_pyfile('application.cfg.py') app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER app.config['FILES_FOLDER'] = FILES_FOLDER +oauth.register( + name='authentik', + client_id='gh8rLyXC6hfI7W5mDX26OJFGHxmU0nMzeYl3B04G', + client_secret='aRHyAkDDeU22s69Ig0o7f46Xn3HCnB8guZoMHuA23B7x1e2YL8FhAqZbu1f3naiaLyTLi9ICIiBc6dxOp5eIO4fEI9paL2NwKXmqYCRmzNzWAfwmcsIh2qTlQfAfsh6e', + access_token_url="https://auth.treehousefullofstars.com/application/o/token/", + authorize_url="https://auth.treehousefullofstars.com/application/o/authorize/", + userinfo_endpoint="https://auth.treehousefullofstars.com/application/o/userinfo/", + api_base_url="https://auth.treehousefullofstars.com/application/o/", + jwks_uri="https://auth.treehousefullofstars.com/application/o/pantry/jwks/", + client_kwargs={'scope': 'openid profile email'}, +) + assets = Environment(app) app.secret_key = '11gs22h2h1a4h6ah8e413a45' -app.register_blueprint(user_api.login_app) +app.register_blueprint(access_api.access_api, url_prefix="/access") app.register_blueprint(administration_api.admin_api, url_prefix='/admin') app.register_blueprint(items_API.items_api, url_prefix='/items') app.register_blueprint(poe_api.point_of_ease, url_prefix='/poe') @@ -89,9 +103,9 @@ def subscribe(): return render_template("subscribe.html") @app.route("/") -@login_required +@access_api.login_required def home(): - update_session_user() + access_api.update_session_user() sites = [site[1] for site in main.get_sites(session['user']['sites'])] session['selected_site'] = sites[0] return redirect("/items")