diff --git a/app/api/items.py b/app/api/items.py index cac40ab37298ac179363b10f9e7d74d49282f471..792a27fce038321e9dfb04a4c6dda0be51488746 100644 --- a/app/api/items.py +++ b/app/api/items.py @@ -11,7 +11,7 @@ ITEM_DOES_NOT_EXISTS_MESSAGE = 'Nincs ilyen termék!' # Endpoint handeler blueprint létrehozása items = Blueprint('items', __name__, url_prefix="/items") -# /api/items +# /api/vX/items # GET # Termékek listáján ak lekérdezése @items.route('/', methods=['GET']) def get_items(): @@ -52,7 +52,7 @@ def get_items(): item_list = [user.to_dict() for user in items] return jsonify({"success": True, "data": item_list}), 200 -# /api/items +# /api/vX/items # POST # Új termék hozzáadása @items.route('/', methods=['POST']) @token_required(required_role=Roles.Admin.value) @@ -131,7 +131,7 @@ def new_item(): return jsonify({"success": True, "message": "Tétel sikeresen hozzáadva!"}), 200 -# /api/items/{item_id} +# /api/vX/items/{item_id} # GET # Termék adatainak lekérdezése az id-je alapján azonosítva @items.route('/<string:item_id>', methods=['GET']) def get_item(item_id): @@ -166,7 +166,7 @@ def get_item(item_id): return jsonify({"success": True, "data": selected_item.to_dict()}), 200 -# /api/items/{item_id} +# /api/vX/items/{item_id} # PUT # Termék adatainak módosítása az id-je alapján azonosítva @items.route('/<string:item_id>', methods=['PUT']) @token_required(required_role=Roles.Admin.value) @@ -251,7 +251,7 @@ def update_item(item_id): return jsonify({"success": True, "message": f"{item_name} sikeresen módosítva!"}) -# /api/items/{item_id}/stock +# /api/vX/items/{item_id}/stock # PUT # Termék készletének módosítása @items.route('/<string:item_id>/stock', methods=['PUT']) @token_required(required_role=Roles.Admin.value) diff --git a/app/api/transactions.py b/app/api/transactions.py new file mode 100644 index 0000000000000000000000000000000000000000..d9cbe6dc801875a5db9c18fb40ebe3062831d760 --- /dev/null +++ b/app/api/transactions.py @@ -0,0 +1,303 @@ +from flask import Blueprint, redirect, render_template, request, flash, jsonify, url_for, Response +from ..token import get_current_user, token_required +from ..functions import validate_json_fields +from ..config import * +from ..database import db +from ..models import Icon, User, Item, Transaction + +ICON_DOES_NOT_EXISTS_MESSAGE = 'Nincs ilyen ikon!' +ITEM_DOES_NOT_EXISTS_MESSAGE = 'Nincs ilyen termék!' + +# Endpoint handeler blueprint létrehozása +transactions = Blueprint('transactions', __name__, url_prefix='/transactions') + +# /api/vX/transactions +# GET # Összes tranzakció lekérdezése +@transactions.route('/', methods=['GET']) +@token_required() +def get_all_transactions(): + """ + Összes tranzakció lekérdezése + --- + tags: + - Tranzakciók + summary: Összes tranzakció lekérdezése bejelentkezett felhasználóként. + description: A felhasználó lekérheti az összes tranzakcióját. + responses: + 200: + description: Sikeres lekérdezés. + 400: + description: Hibás kérés. + security: + - jwtCookieAuth: [] + """ + + if request.method == 'GET': + transactions = Transaction.query.all() + return jsonify({"success": True, "data": [transaction.to_dict() for transaction in transactions]}) + +# /api/vX/transactions +# POST # Új tranzakció létrehozása (Termék megvétele) +@transactions.route('/', methods=['POST']) +@token_required() +def create_transaction(): + """ + Új tranzakció létrehozása + --- + tags: + - Tranzakciók + summary: Új tranzakció létrehozása bejelentkezett felhasználóként. + description: A felhasználó új tranzakciót hozhat létre. + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + item_id: + type: integer + description: A tranzakcióhoz kapcsolódó termék azonosítója. + quantity: + type: integer + description: A tranzakcióhoz kapcsolódó mennyiség. + responses: + 200: + description: Sikeres tranzakció létrehozás. + 400: + description: Hibás kérés. + security: + - jwtCookieAuth: [] + """ + if request.method == 'POST': + current_user = get_current_user() + data = request.json + + is_valid, msg = validate_json_fields(data, ['item_id', 'quantity'], ['item_id', 'quantity']) + if not is_valid: + return jsonify({"success": False, 'message': msg}), 400 + + # request tartalmának kiolvasása + item_id = int(str(data.get('item_id'))) + quantity = int(str(data.get('quantity'))) + + if quantity < 1: + return jsonify({"success": False, "message": "Nem lehet 1-nél kevesebb mennyiséget venni!"}), 400 + + # Lekérdezzük az adatbázisbólk, hogy van e ilyen termék + selected_item = Item.query.filter_by(id=item_id).first() + + # Ha nincs ilyen termék, akkor errort dobunk vissza + if not selected_item: + return jsonify({"success": False, "message": ITEM_DOES_NOT_EXISTS_MESSAGE}), 400 + + # Fizetendő összeg kiszámolása + amount = selected_item.price * quantity + + # Ha nincs elég egyenlege a felhasználónak, akkor errort dobunk vissza + if current_user.balance - amount < current_user.credit * -1: + return jsonify({"success": False, "message": "A vásárlás meghaladja az egyenleged!\nTölts fel pénzt BecskasszaSCH-ra!"}), 400 + + # A termék készletének csökkentése + if selected_item.stock != ItemStates.INFINITY.value: + # Többet venne, mint amennyi van készleten akkor hiba + if selected_item.stock - quantity < 0: + return jsonify({"success": False, "message": f"Nincs ennyi készleten a(z) {selected_item.name} termékből!"}), 400 + selected_item.stock -= quantity + + # Tranzakció felvétele a táblába + new_transaction = Transaction( + by_user_uuid = current_user.uuid, + to_user_uuid = BECSKASSZA_BOT.uuid, + item_id = item_id, + amount = -amount, + quantity = quantity + ) + + # Tranzakció hozzáadása az adatbázishoz + db.session.add(new_transaction) + # A kiválasztott felhasználó egyenlegének csökkentése + current_user.balance -= amount + # Tranzakció hozzáadása a bot felhasználóhoz + becskassza_bot = User.query.get(BECSKASSZA_BOT.uuid) + becskassza_bot.balance += amount + + # Adatbázisba mentés + db.session.commit() + + return jsonify({"success": True, "message": f"Sikeres tranzakció: {selected_item.name}: {selected_item.price} JMF * {quantity} db = {amount} JMF!"}) + +# /api/vX/transactions/{transaction_id} +# DELETE # Tranzakció visszavonása (törlése) +@transactions.route('/<int:transaction_id>', methods=['DELETE']) +@token_required(required_role=Roles.Admin.value) +def delete_transaction(transaction_id): + """ + Tranzakció törlése + --- + tags: + - Tranzakciók + summary: Tranzakció törlése admin jogosultsággal. + description: Az admin felhasználó törölheti a tranzakciót. + parameters: + - name: transaction_id + in: path + required: true + description: A törlendő tranzakció azonosítója. + schema: + type: integer + responses: + 200: + description: Sikeres törlés. + 400: + description: Hibás kérés vagy érvénytelen adatok. + security: + - jwtCookieAuth: [] + """ + + if request.method == 'DELETE': + # Lekérdezzük az adatbázisbólk, hogy van e ilyen termék + selected_transaction = Transaction.query.get(transaction_id) + # Ha nincs ilyen termék, akkor errort dobunk vissza + if not selected_transaction: + return jsonify({"success": False, "message": "Nincs ilyen tranzakció"}), 400 + + by_user = User.query.get(selected_transaction.by_user_uuid) + to_user = User.query.get(selected_transaction.to_user_uuid) + + # Ha nincs ilyen felhasználó, akkor errort dobunk visza + if not by_user or not to_user: + return jsonify({"success": False, "message": "Hiba a tranzakcióhoz tartozó felhasználók lekérdezésekor"}), 400 + + # A tranzakciót elszenvedő felhasználó egyenlegének növelése + by_user.balance += selected_transaction.amount + # A tranzakcióban kedvezményezett felhasználó egyenlegének csökkentése + to_user.balance -= selected_transaction.amount + + # A termék lekérdezése + selected_item = Item.query.get(selected_transaction.item_id) + if not selected_item: + return jsonify({"success": False, "message": ITEM_DOES_NOT_EXISTS_MESSAGE}), 400 + + # Ha a termék készlete végtelen, akkor nem növeljük a készletét + if selected_item.stock != ItemStates.INFINITY.value: + selected_item.stock += selected_transaction.quantity + + # Tranzakció eltávolítása a táblából + db.session.delete(selected_transaction) + + # Adatbázisba mentés + db.session.commit() + + return jsonify({"success": True, "message": f"A tranzakció sikeresen eltávolítva!"}) + +# /api/vX/transactions/order +# POST # Pultos rendelés összeállítása a felhasználónak +@transactions.route('/order', methods=['POST']) +@token_required(required_role=Roles.Bartender.value) +def create_order(): + """ + Pultos rendelés létrehozása + --- + tags: + - Tranzakciók + summary: Pultos rendelés létrehozása admin jogosultsággal. + description: Az admin felhasználó létrehozhat egy rendelést a felhasználónak. + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + user_id: + type: string + description: A felhasználó azonosítója, akinek a rendelést készítjük. + order: + type: array + items: + type: object + properties: + item_id: + type: integer + description: A termék azonosítója. + quantity: + type: integer + description: A rendelt mennyiség. + responses: + 200: + description: Sikeres rendelés létrehozás. + 400: + description: Hibás kérés vagy érvénytelen adatok. + security: + - jwtCookieAuth: [] + """ + if request.method == 'POST': + current_user = get_current_user() + data = request.json + + is_valid, msg = validate_json_fields(data, ['user_id', 'order']) + if not is_valid: + return jsonify({"success": False, 'message': msg}), 400 + + # request tartalmának kiolvasása + # bartender_user_id = str(data.get('bartenderId')) + bartender_user_id = current_user.uuid + for_user_id = str(data.get('user_id')) + order = data.get('order') + + for key, item in order.items(): + if not (str(item['item_id']).isdigit() and str(item['quantity']).isdigit()): + return jsonify({"success": False, 'message': "Nem minden termék adat helyes!"}), 400 + + item_id = int(str(item['item_id'])) + quantity = int(str(item['quantity'])) + + # Lekérdezzük az adatbázisbólk, hogy van e ilyen termék + selected_item = Item.query.get(item_id) + if not selected_item: + return jsonify({"success": False, "message": "Nincs ilyen termék"}), 400 + + # Lekérdezzük az adatbázisbólk, hogy van e ilyen nevű felhasználó + selected_user = User.query.get(for_user_id) + bartender_user = User.query.get(bartender_user_id) + # Ha nincs ilyen felhasználó, akkor errort dobunk visza + if (not selected_user) or (not bartender_user): + return jsonify({"success": False, "message": "Nem létezik ilyen felhasználó"}), 400 + # Elégtelen jogosultság esetén hibát dobunk + if bartender_user.role < Roles.Bartender.value: + return jsonify({"success": False, "message": f"{bartender_user.name} felhasználónak nincs jogosultsága ilyen művelethez!"}), 400 + + + amount = selected_item.price * quantity + + if selected_user.balance - amount < selected_user.credit * -1: + return jsonify({"success": False, "message": f"A vásárlás meghaladja {selected_user.name} egyenlegét!\nTöltsön fel pénzt BecskasszaSCH-ra!"}), 400 + + # A termék készletének csökkentése + if selected_item.stock != ItemStates.INFINITY.value: + # Többet venne, mint amennyi van készleten akkor hiba + if selected_item.stock - quantity < 0: + return jsonify({"success": False, "message": f"Nincs ennyi készleten a(z) {selected_item.name} termékből!"}), 400 + selected_item.stock -= quantity + + # Tranzakció felvétele a táblába + order_transaction = Transaction( + by_user_uuid = selected_user.uuid, + to_user_uuid = BECSKASSZA_BOT.uuid, + item_id = item_id, + amount = -amount, + quantity = quantity, + note = f"By: {bartender_user.name}" + ) + + db.session.add(order_transaction) + # A kiválasztott felhasználó egyenlegének csökkentése + selected_user.balance -= amount + # Tranzakció hozzáadása a bot felhasználóhoz + becskassza_bot = User.querry.get(BOT_USER_UUID) + becskassza_bot.balance += amount + + # Adatbázisba mentés + db.session.commit() + return jsonify({"success": True, "message": "Sikeresen rendelés!"}) \ No newline at end of file diff --git a/app/api/users.py b/app/api/users.py index 656581b21010e1893a7dfe11bf95fd291e1dfe74..7312da73912b08542f02871bfff05fcc404557a9 100644 --- a/app/api/users.py +++ b/app/api/users.py @@ -8,11 +8,11 @@ from ..config import * USER_DOES_NOT_EXISTS_MESSAGE = 'Nem létezik ilyen felhasználó!' # Endpoint handeler blueprint létrehozása -users = Blueprint('users', __name__, url_prefix='/users') # prefix: /api/v1/users +users = Blueprint('users', __name__, url_prefix='/users') # prefix: /api/vX/v1/users calc_credit = lambda role: ADMIN_CREDIT if role == Roles.Admin.value else USER_CREDIT -# /api/users +# /api/vX/users # Felhasználók lekérdezése @users.route('/', methods=['GET']) @token_required(required_role=Roles.Admin.value) @@ -72,7 +72,7 @@ def get_users(): user_list = [user.to_dict() for user in users] return jsonify({"success": True, "data": user_list}), 200 -# /api/users +# /api/vX/users # Felhasználó létrehozása @users.route('/', methods=['POST']) @token_required(required_role=Roles.Admin.value) @@ -166,7 +166,7 @@ def create_user(): return jsonify({"success": True, "message": f"{user_name} felhasználó sikeresen létrehozva."}), 200 -# /api/users/{uuid} +# /api/vX/users/{uuid} # GET # Felhasználói adatok lekérdezése uuid-val azonosítva @users.route('/<string:uuid>', methods=['GET']) @token_required(required_role=Roles.Admin.value) @@ -299,7 +299,7 @@ def delete_user(uuid): return jsonify({"success": True, "message": f"{selected_user.name} felhasználó sikeresen törölve."}), 200 -# /api/users/{uuid}/balance +# /api/vX/users/{uuid}/balance # GET # Felhasználó egyenlegének lekérdezése @users.route('/<string:uuid>/balance', methods=['GET']) @token_required(required_role=Roles.Admin.value) @@ -456,7 +456,7 @@ def update_user_balance(uuid): return jsonify({"success": True, "message": f"{amount} JMF sikeresen feltöltve {selected_user.name}-nek!"}), 200 -# /api/users/{uuid}/transactions +# /api/vX/users/{uuid}/transactions # GET # Felhasználó által elszenvedett tranzakciók listája @users.route('/<string:uuid>/transactions', methods=['GET']) # @token_required(required_role=Roles.admin.value) @@ -536,7 +536,7 @@ def get_user_transactions(uuid): return jsonify({"success": True, "transactions": transaction_list}), 200 -# /api/users/{uuid}/role +# /api/vX/users/{uuid}/role # GET # Visszaadja a uuid-vel azonosított felhasználó rangját @users.route('/<string:uuid>/role', methods=['GET']) @token_required(required_role=Roles.Admin.value) @@ -592,7 +592,7 @@ def get_user_role(uuid): return jsonify({"success": True, "role": selected_user.role, "role_name": f"{Roles(selected_user.role)}" }), 200 -# /api/users/{uuid}/role +# /api/vX/users/{uuid}/role # PATCH # Megváltoztatja a felhasználó rangját @users.route('/<string:uuid>/role', methods=['PATCH']) @token_required(required_role=Roles.Admin.value) diff --git a/app/app.py b/app/app.py index c53abe0e37ea77947429eeda41a868b3de5aec35..ab2997748b2277bf5cf43e7d5f0565d9fbd3c8cd 100644 --- a/app/app.py +++ b/app/app.py @@ -98,72 +98,47 @@ app.register_blueprint(icon_requests, url_prefix='/') # ikon műveletek endpoint # === REST API endpointok === from .api.users import users from .api.items import items -app.register_blueprint(users, url_prefix= API_PREFIX + users.url_prefix) # oldalak endpoinjainak hozzáakpcsolása az apphoz -app.register_blueprint(items, url_prefix= API_PREFIX + items.url_prefix) # oldalak endpoinjainak hozzáakpcsolása az apphoz +from .api.transactions import transactions +# REST API endpointok hozzárendelése a Flask app-hoz +app.register_blueprint(users, url_prefix= API_PREFIX + users.url_prefix) +app.register_blueprint(items, url_prefix= API_PREFIX + items.url_prefix) +app.register_blueprint(transactions, url_prefix= API_PREFIX + transactions.url_prefix) # === Adatbázis inizializálása === with app.app_context(): # Kontextus beállítása # = BecskassszaBot felhasználó létrehozása = - bot = User.query.filter_by(uuid=BOT_USER_UUID).first() + bot = User.query.get(BECSKASSZA_BOT.uuid) if not bot: - becskasssza_bot = User( - uuid = BOT_USER_UUID, - name = BOT_USER_NAME, - role = BOT_USER_ROLE - ) # Adatbázisba írás - db.session.add(becskasssza_bot) + db.session.add(BECSKASSZA_BOT) db.session.commit() print('[INFO] BecskasszaBot created') + # = Default ikon hozzáadása az adatbáizishoz = - if not Icon.query.get(DEFAULT_ICON_ID): - default_icon = Icon( - id = DEFAULT_ICON_ID, - name = DEFAULT_ICON_NAME, - url = DEFAULT_ICON_URL, - data=open(DEFAULT_STATIC_FOLDER + DEFAULT_ICON_URL, "rb").read() - ) + if not Icon.query.get(DEFAULT_ICON.id): # Adatbázisba írás - db.session.add(default_icon) + db.session.add(DEFAULT_ICON) db.session.commit() print('[INFO] Default icon created') + # = Default feltöltés ikon hozzáadása az adatbázishoz = - if not Icon.query.get(TOPUP_ICON_ID): - topup_icon = Icon( - id = TOPUP_ICON_ID, - name = TOPUP_NAME, - url = TOPUP_ICON_URL, - data=open(DEFAULT_STATIC_FOLDER + TOPUP_ICON_URL, "rb").read() - ) + if not Icon.query.get(TOPUP_ICON.id): # Adatbázisba írás - db.session.add(topup_icon) + db.session.add(TOPUP_ICON) db.session.commit() print('[INFO] Topup icon created') + # = Feltöltés termék hozzáadása az adatbázishoz = - if not Item.query.get(TOPUP_ITEM_ID): - topup_item = Item( - id=TOPUP_ITEM_ID, - name=TOPUP_NAME, - price=1, - # unit=TOPUP_UNIT, - icon_id=TOPUP_ICON_ID, - stock=ItemStates.INACTIVE.value - ) + if not Item.query.get(TOPUP_ITEM.id): # Adatbázisba írás - db.session.add(topup_item) + db.session.add(TOPUP_ITEM) db.session.commit() print('[INFO] Topup item created') + # = Vásárlás termék hozzáadása az adatbázishoz = - if not Item.query.get(BUY_ITEM_ID): - buy_item = Item( - id=BUY_ITEM_ID, - name=BUY_NAME, - price=1, - icon_id=BUY_ICON_ID, - stock=ItemStates.INACTIVE.value - ) + if not Item.query.get(BUY_ITEM.id): # Adatbázisba írás - db.session.add(buy_item) + db.session.add(BUY_ITEM) db.session.commit() print('[INFO] Buy item created') \ No newline at end of file diff --git a/app/config.py b/app/config.py index 2660961f44d609d239986e436b5c14adbaab51a1..2401e2f1e2e240f66ec04ff26c44f0bf46e358f2 100644 --- a/app/config.py +++ b/app/config.py @@ -1,5 +1,6 @@ # === Konstansok === import os +from app.models import * from dotenv import load_dotenv from enum import Enum @@ -18,6 +19,23 @@ class Roles(Enum): return self.value +# Termékek elérhetőségének kategorizálása a 'Item.stock' property által +class ItemStates(Enum): + AVAILABLE = 1 + SOLD_OUT = 0 + INFINITY = -1 + INACTIVE = -2 + ARCHIVED = -3 + + ONE_TIME = -4 + ONE_TIME_PURCHASED = -5 + + def __str__(self): + return self.name + def __int__(self): + return self.value + + # Alap konstansok API_PREFIX = '/api/v1' DEBUG_MODE = os.getenv('DEBUG_MODE', 'False') @@ -30,47 +48,55 @@ UPLOAD_FOLDER = DEFAULT_STATIC_FOLDER + ICONS_FOLDER USER_CREDIT = 0 #5_000 ADMIN_CREDIT = 0 #15_000 TRANSACTION_AMOUNT_LIMIT = 100_000 +DEFAULT_UNIT = 'db' # Az alapvető mértékenysége a terméknek # Ez az ID szám alatt nem lesznek kilistázva a dolgok NON_DISPLAY_BELLOW_ID = 0 # Becskassza dummy Felhasználó -BOT_USER_UUID = 'B3C5K4SS24-B0T-M452T3R' -BOT_USER_NAME = 'BecskasszaBot' -BOT_USER_ROLE = Roles.Bot.value -# Pénz Feltöltés ikon és a tranzakció hozzárendeléshez tartozó termék -TOPUP_ITEM_ID = 0 -TOPUP_ICON_ID = 0 +BECSKASSZA_BOT = User( + uuid='B3C5K4SS24-B0T-M452T3R', + name='BecskasszaBot', + balance=0, + credit=0, + role=Roles.Bot.value +) + +# Pénz Feltöltés ikon TOPUP_ICON_URL = 'imgs/kszkoin.svg' -TOPUP_NAME = 'Feltöltés' -TOPUP_UNIT = '' +TOPUP_ICON = Icon ( + id=0, + name='Feltöltés', + url=TOPUP_ICON_URL, + data=open(DEFAULT_STATIC_FOLDER + TOPUP_ICON_URL, "rb").read() +) # Alap ikon a termékekhez ha nem adnak meg ikont -DEFAULT_ICON_ID = 1 DEFAULT_ICON_URL = 'imgs/drink.png' -DEFAULT_ICON_NAME = 'Alap ikon' +DEFAULT_ICON = Icon ( + id=1, + name='Alap ikon', + url=DEFAULT_ICON_URL, + data=open(DEFAULT_STATIC_FOLDER + DEFAULT_ICON_URL, "rb").read() +) -# Az alapvető mértékenysége a terméknek -DEFAULT_UNIT = 'db' +# Feltöltés termék +TOPUP_ITEM = Item ( + id=0, + name='Feltöltés', + price=1, + unit='', + icon_id=TOPUP_ICON.id, + stock=ItemStates.INACTIVE.value +) # Ha törlődnik egy termék, akkor minden ahhoz kapcsolódó tranzakció ehhez a default vásárlás termékre fog átállni -BUY_ITEM_ID = -1 -BUY_ICON_ID = DEFAULT_ICON_ID -BUY_NAME = 'Vásárlás' - -# Termékek elérhetőségének kategorizálása a 'Item.stock' property által -class ItemStates(Enum): - AVAILABLE = 1 - SOLD_OUT = 0 - INFINITY = -1 - INACTIVE = -2 - ARCHIVED = -3 - - ONE_TIME = -4 - ONE_TIME_PURCHASED = -5 - - def __str__(self): - return self.name - def __int__(self): - return self.value +BUY_ITEM = Item ( + id=-1, + name='Vásárlás', + price=1, + unit='', + icon_id=DEFAULT_ICON.id, + stock=ItemStates.INACTIVE.value +) \ No newline at end of file diff --git a/app/models.py b/app/models.py index 6f217d5025f388414f61f4df43eef3e26ad58598..c91e4b41067d23867bff192cd9a0d528c25e6df5 100644 --- a/app/models.py +++ b/app/models.py @@ -1,7 +1,5 @@ -# from .database import db from datetime import datetime, timezone from flask_login import UserMixin -from sqlalchemy import DateTime from sqlalchemy.sql import func from .database import db