aiomysql refactor

This commit is contained in:
David Rodenkirchen 2024-09-03 14:30:32 +02:00
parent a9597b5c4f
commit 30b32a4c02
24 changed files with 901 additions and 755 deletions

Binary file not shown.

View File

@ -1,4 +1,6 @@
import logging import logging
from asyncio import get_event_loop
import sys import sys
from pathlib import Path from pathlib import Path
@ -27,13 +29,7 @@ if __name__ == "__main__":
corner_radius_large=0, corner_radius_large=0,
font=Font(from_root("src/ez_lan_manager/assets/fonts/joystix.otf")) font=Font(from_root("src/ez_lan_manager/assets/fonts/joystix.otf"))
) )
services = init_services()
try:
services = init_services()
except NoDatabaseConnectionError:
logger.fatal("Could not connect to database, exiting...")
sys.exit(1)
lan_info = services[2].get_lan_info() lan_info = services[2].get_lan_info()
@ -41,6 +37,12 @@ if __name__ == "__main__":
await session.set_title(lan_info.name) await session.set_title(lan_info.name)
session.attach(SessionStorage()) session.attach(SessionStorage())
async def on_app_start(a: App) -> None:
init_result = await a.default_attachments[3].init_db_pool()
if not init_result:
logger.fatal("Could not connect to database, exiting...")
sys.exit(1)
app = App( app = App(
name="EZ LAN Manager", name="EZ LAN Manager",
pages=[ pages=[
@ -138,6 +140,7 @@ if __name__ == "__main__":
assets_dir=Path(__file__).parent / "assets", assets_dir=Path(__file__).parent / "assets",
default_attachments=services, default_attachments=services,
on_session_start=on_session_start, on_session_start=on_session_start,
on_app_start=on_app_start,
icon=from_root("src/ez_lan_manager/assets/img/favicon.png"), icon=from_root("src/ez_lan_manager/assets/img/favicon.png"),
meta_tags={ meta_tags={
"robots": "INDEX,FOLLOW", "robots": "INDEX,FOLLOW",

View File

@ -1,10 +1,8 @@
from datetime import datetime from datetime import datetime
from typing import Callable
import rio import rio
from rio import Component, Row, Text, IconButton, TextStyle, Color from rio import Component, Row, Text, TextStyle, Color
from src.ez_lan_manager import AccountingService
from src.ez_lan_manager.types.CateringOrder import CateringOrderStatus from src.ez_lan_manager.types.CateringOrder import CateringOrderStatus
MAX_LEN = 24 MAX_LEN = 24

View File

@ -6,19 +6,13 @@ from src.ez_lan_manager.components.UserInfoAndLoginBox import UserInfoAndLoginBo
from src.ez_lan_manager.types.SessionStorage import SessionStorage from src.ez_lan_manager.types.SessionStorage import SessionStorage
class DesktopNavigation(Component): class DesktopNavigation(Component):
def __post_init__(self) -> None:
self.session[SessionStorage].subscribe_to_logged_in_or_out_event(self.__class__.__name__, self.refresh_cb)
async def refresh_cb(self) -> None:
await self.force_refresh()
def build(self) -> Component: def build(self) -> Component:
lan_info = self.session[ConfigurationService].get_lan_info() lan_info = self.session[ConfigurationService].get_lan_info()
return Card( return Card(
Column( Column(
Text(lan_info.name, align_x=0.5, margin_top=0.3, style=TextStyle(fill=self.session.theme.hud_color, font_size=2.5)), Text(lan_info.name, align_x=0.5, margin_top=0.3, style=TextStyle(fill=self.session.theme.hud_color, font_size=2.5)),
Text(f"Edition {lan_info.iteration}", align_x=0.5, style=TextStyle(fill=self.session.theme.hud_color, font_size=1.2), margin_top=0.3, margin_bottom=2), Text(f"Edition {lan_info.iteration}", align_x=0.5, style=TextStyle(fill=self.session.theme.hud_color, font_size=1.2), margin_top=0.3, margin_bottom=2),
UserInfoAndLoginBox(refresh_cb=self.refresh_cb), UserInfoAndLoginBox(),
DesktopNavigationButton("News", "./news"), DesktopNavigationButton("News", "./news"),
Spacer(min_height=1), Spacer(min_height=1),
DesktopNavigationButton(f"Über {lan_info.name} {lan_info.iteration}", "./overview"), DesktopNavigationButton(f"Über {lan_info.name} {lan_info.iteration}", "./overview"),

View File

@ -1,3 +1,5 @@
from typing import Optional
import rio import rio
from rio import Component, Column, Text, TextStyle, Button, Row, ScrollContainer, Spacer from rio import Component, Column, Text, TextStyle, Button, Row, ScrollContainer, Spacer
@ -11,9 +13,11 @@ from src.ez_lan_manager.types.SessionStorage import SessionStorage
class ShoppingCartAndOrders(Component): class ShoppingCartAndOrders(Component):
show_cart: bool = True show_cart: bool = True
orders: list[CateringOrder] = []
async def switch(self) -> None: async def switch(self) -> None:
self.show_cart = not self.show_cart self.show_cart = not self.show_cart
self.orders = await self.session[CateringService].get_orders_for_user(self.session[SessionStorage].user_id)
async def on_remove_item(self, list_id: int) -> None: async def on_remove_item(self, list_id: int) -> None:
catering_service = self.session[CateringService] catering_service = self.session[CateringService]
@ -36,7 +40,7 @@ class ShoppingCartAndOrders(Component):
if not user_id: if not user_id:
return return
cart = catering_service.get_cart(user_id) cart = catering_service.get_cart(user_id)
cart.append(catering_service.get_menu_item_by_id(article_id)) cart.append(await catering_service.get_menu_item_by_id(article_id))
catering_service.save_cart(user_id, cart) catering_service.save_cart(user_id, cart)
await self.force_refresh() await self.force_refresh()
@ -99,14 +103,13 @@ class ShoppingCartAndOrders(Component):
) )
) )
else: else:
orders = catering_service.get_orders_for_user(user_id)
orders_container = ScrollContainer( orders_container = ScrollContainer(
content=Column( content=Column(
*[CateringOrderItem( *[CateringOrderItem(
order_id=order_item.order_id, order_id=order_item.order_id,
order_datetime=order_item.order_date, order_datetime=order_item.order_date,
order_status=order_item.status, order_status=order_item.status,
) for order_item in orders], ) for order_item in self.orders],
Spacer(grow_y=True) Spacer(grow_y=True)
), ),
min_height=8, min_height=8,

View File

@ -1,15 +1,17 @@
import logging import logging
from random import choice from random import choice
from typing import Callable from typing import Optional
from rio import Component, Column, Text, Row, Rectangle, Button, TextStyle, Color, Spacer, TextInput, Link from rio import Component, Column, Text, Row, Rectangle, Button, TextStyle, Color, Spacer, TextInput, Link, event
from src.ez_lan_manager import UserService from src.ez_lan_manager import UserService
from src.ez_lan_manager.components.UserInfoBoxButton import UserInfoBoxButton from src.ez_lan_manager.components.UserInfoBoxButton import UserInfoBoxButton
from src.ez_lan_manager.services.AccountingService import AccountingService from src.ez_lan_manager.services.AccountingService import AccountingService
from src.ez_lan_manager.services.DatabaseService import NoDatabaseConnectionError, DatabaseService
from src.ez_lan_manager.services.TicketingService import TicketingService from src.ez_lan_manager.services.TicketingService import TicketingService
from src.ez_lan_manager.services.SeatingService import SeatingService from src.ez_lan_manager.services.SeatingService import SeatingService
from src.ez_lan_manager.types.Seat import Seat
from src.ez_lan_manager.types.Ticket import Ticket
from src.ez_lan_manager.types.User import User
from src.ez_lan_manager.types.SessionStorage import SessionStorage from src.ez_lan_manager.types.SessionStorage import SessionStorage
logger = logging.getLogger(__name__.split(".")[-1]) logger = logging.getLogger(__name__.split(".")[-1])
@ -39,9 +41,20 @@ class StatusButton(Component):
class UserInfoAndLoginBox(Component): class UserInfoAndLoginBox(Component):
refresh_cb: Callable
TEXT_STYLE = TextStyle(fill=Color.from_hex("02dac5"), font_size=0.9) TEXT_STYLE = TextStyle(fill=Color.from_hex("02dac5"), font_size=0.9)
show_login: bool = True show_login: bool = True
user: Optional[User] = None
user_balance: Optional[int] = 0
user_ticket: Optional[Ticket] = None
user_seat: Optional[Seat] = None
@event.on_populate
async def async_init(self) -> None:
if self.session[SessionStorage].user_id:
self.user = await self.session[UserService].get_user(self.session[SessionStorage].user_id)
self.user_balance = await self.session[AccountingService].get_balance(self.user.user_id)
self.user_ticket = await self.session[TicketingService].get_user_ticket(self.user.user_id)
self.user_seat = await self.session[SeatingService].get_user_seat(self.user.user_id)
@staticmethod @staticmethod
def get_greeting() -> str: def get_greeting() -> str:
@ -64,13 +77,13 @@ class UserInfoAndLoginBox(Component):
async def _on_login_pressed(self) -> None: async def _on_login_pressed(self) -> None:
user_name = self.user_name_input.text.lower() user_name = self.user_name_input.text.lower()
if self.session[UserService].is_login_valid(user_name, self.password_input.text): if await self.session[UserService].is_login_valid(user_name, self.password_input.text):
self.user_name_input.is_valid = True self.user_name_input.is_valid = True
self.password_input.is_valid = True self.password_input.is_valid = True
self.login_button.is_loading = False self.login_button.is_loading = False
await self.session[SessionStorage].set_user_id(self.session[UserService].get_user(user_name).user_id) await self.session[SessionStorage].set_user_id((await self.session[UserService].get_user(user_name)).user_id)
await self.async_init()
self.show_login = False self.show_login = False
await self.refresh_cb()
else: else:
self.user_name_input.is_valid = False self.user_name_input.is_valid = False
self.password_input.is_valid = False self.password_input.is_valid = False
@ -114,7 +127,7 @@ class UserInfoAndLoginBox(Component):
on_press=lambda: self.session.navigate_to("./forgot-password") on_press=lambda: self.session.navigate_to("./forgot-password")
) )
if self.show_login and self.session[SessionStorage].user_id is None: if self.user is None and self.session[SessionStorage].user_id is None:
return Rectangle( return Rectangle(
content=Column( content=Column(
self.user_name_input, self.user_name_input,
@ -139,25 +152,31 @@ class UserInfoAndLoginBox(Component):
margin_top=0.3, margin_top=0.3,
margin_bottom=2 margin_bottom=2
) )
elif self.user is None and self.session[SessionStorage].user_id is not None:
return Rectangle(
content=Column(),
fill=Color.TRANSPARENT,
min_height=8,
min_width=12,
align_x=0.5,
margin_top=0.3,
margin_bottom=2
)
else: else:
user = self.session[UserService].get_user(self.session[SessionStorage].user_id)
if user is None:
logger.warning("User could not be found, this should not have happend.")
a_s = self.session[AccountingService]
return Rectangle( return Rectangle(
content=Column( content=Column(
Text(f"{self.get_greeting()},", style=TextStyle(fill=Color.from_hex("02dac5"), font_size=0.9), justify="center"), Text(f"{self.get_greeting()},", style=TextStyle(fill=Color.from_hex("02dac5"), font_size=0.9), justify="center"),
Text(f"{user.user_name}", style=TextStyle(fill=Color.from_hex("02dac5"), font_size=1.2), justify="center"), Text(f"{self.user.user_name}", style=TextStyle(fill=Color.from_hex("02dac5"), font_size=1.2), justify="center"),
Row( Row(
StatusButton(label="TICKET", target_url="./buy_ticket", StatusButton(label="TICKET", target_url="./buy_ticket",
enabled=self.session[TicketingService].get_user_ticket(user.user_id) is not None), enabled=self.user_ticket is not None),
StatusButton(label="SITZPLATZ", target_url="./seating", StatusButton(label="SITZPLATZ", target_url="./seating",
enabled=self.session[SeatingService].get_user_seat(user.user_id) is not None), enabled=self.user_seat is not None),
proportions=(50, 50), proportions=(50, 50),
grow_y=False grow_y=False
), ),
UserInfoBoxButton("Profil bearbeiten", "./edit-profile"), UserInfoBoxButton("Profil bearbeiten", "./edit-profile"),
UserInfoBoxButton(f"Guthaben: {a_s.make_euro_string_from_int(a_s.get_balance(user.user_id))}", "./account"), UserInfoBoxButton(f"Guthaben: {self.session[AccountingService].make_euro_string_from_int(self.user_balance)}", "./account"),
Button( Button(
content=Text("Ausloggen", style=TextStyle(fill=Color.from_hex("02dac5"), font_size=0.6)), content=Text("Ausloggen", style=TextStyle(fill=Color.from_hex("02dac5"), font_size=0.6)),
shape="rectangle", shape="rectangle",

View File

@ -1,22 +1,47 @@
from rio import Column, Component, event, Text, TextStyle, Button, Color, Spacer, Revealer, Row from asyncio import sleep
from typing import Optional
from rio import Column, Component, event, Text, TextStyle, Button, Color, Spacer, Revealer, Row, ProgressCircle
from src.ez_lan_manager import ConfigurationService, UserService, AccountingService from src.ez_lan_manager import ConfigurationService, UserService, AccountingService
from src.ez_lan_manager.components.MainViewContentBox import MainViewContentBox from src.ez_lan_manager.components.MainViewContentBox import MainViewContentBox
from src.ez_lan_manager.pages import BasePage from src.ez_lan_manager.pages import BasePage
from src.ez_lan_manager.types.SessionStorage import SessionStorage from src.ez_lan_manager.types.SessionStorage import SessionStorage
from src.ez_lan_manager.types.Transaction import Transaction
from src.ez_lan_manager.types.User import User
class AccountPage(Component): class AccountPage(Component):
user: Optional[User] = None
balance: Optional[int] = None
transaction_history: list[Transaction] = list()
@event.on_populate @event.on_populate
async def on_populate(self) -> None: async def on_populate(self) -> None:
await self.session.set_title(f"{self.session[ConfigurationService].get_lan_info().name} - Guthabenkonto") await self.session.set_title(f"{self.session[ConfigurationService].get_lan_info().name} - Guthabenkonto")
self.user = await self.session[UserService].get_user(self.session[SessionStorage].user_id)
self.balance = await self.session[AccountingService].get_balance(self.user.user_id)
self.transaction_history = await self.session[AccountingService].get_transaction_history(self.user.user_id)
async def _on_banking_info_press(self): async def _on_banking_info_press(self):
self.banking_info_revealer.is_open = not self.banking_info_revealer.is_open self.banking_info_revealer.is_open = not self.banking_info_revealer.is_open
def build(self) -> Component: def build(self) -> Component:
user = self.session[UserService].get_user(self.session[SessionStorage].user_id) if not self.user and not self.balance:
a_s = self.session[AccountingService] return BasePage(
content=Column(
MainViewContentBox(
ProgressCircle(
color="secondary",
align_x=0.5,
margin_top=2,
margin_bottom=2
)
),
align_y = 0,
)
)
self.banking_info_revealer = Revealer( self.banking_info_revealer = Revealer(
header=None, header=None,
content=Column( content=Column(
@ -45,7 +70,7 @@ class AccountPage(Component):
align_x=0.2 align_x=0.2
), ),
Text( Text(
f"AUFLADUNG - {user.user_id} - {user.user_name}", f"AUFLADUNG - {self.user.user_id} - {self.user.user_name}",
style=TextStyle( style=TextStyle(
fill=self.session.theme.neutral_color fill=self.session.theme.neutral_color
), ),
@ -73,7 +98,7 @@ class AccountPage(Component):
) )
) )
for transaction in sorted(self.session[AccountingService].get_transaction_history(user.user_id), key=lambda t: t.transaction_date, reverse=True): for transaction in sorted(self.transaction_history, key=lambda t: t.transaction_date, reverse=True):
transaction_history.add( transaction_history.add(
Row( Row(
Text( Text(
@ -89,7 +114,7 @@ class AccountPage(Component):
align_x=0 align_x=0
), ),
Text( Text(
f"{'-' if transaction.is_debit else '+'}{a_s.make_euro_string_from_int(transaction.value)}", f"{'-' if transaction.is_debit else '+'}{AccountingService.make_euro_string_from_int(transaction.value)}",
style=TextStyle( style=TextStyle(
fill=self.session.theme.danger_color if transaction.is_debit else self.session.theme.success_color, fill=self.session.theme.danger_color if transaction.is_debit else self.session.theme.success_color,
font_size=0.8 font_size=0.8
@ -106,7 +131,7 @@ class AccountPage(Component):
content=Column( content=Column(
MainViewContentBox( MainViewContentBox(
content=Text( content=Text(
f"Kontostand: {a_s.make_euro_string_from_int(a_s.get_balance(user.user_id))}", f"Kontostand: {AccountingService.make_euro_string_from_int(self.balance)}",
style=TextStyle( style=TextStyle(
fill=self.session.theme.background_color, fill=self.session.theme.background_color,
font_size=1.2 font_size=1.2

View File

@ -4,7 +4,7 @@ from typing import * # type: ignore
from rio import Component, event, Spacer, Card, Container, Column, Row, TextStyle, Color, Text from rio import Component, event, Spacer, Card, Container, Column, Row, TextStyle, Color, Text
from src.ez_lan_manager import ConfigurationService, DatabaseService from src.ez_lan_manager import ConfigurationService
from src.ez_lan_manager.components.DesktopNavigation import DesktopNavigation from src.ez_lan_manager.components.DesktopNavigation import DesktopNavigation
class BasePage(Component): class BasePage(Component):
@ -14,11 +14,6 @@ class BasePage(Component):
async def on_window_size_change(self): async def on_window_size_change(self):
await self.force_refresh() await self.force_refresh()
@event.on_populate
async def check_db_connection(self):
if not self.session[DatabaseService].is_connected:
self.session.navigate_to("./db-error")
def build(self) -> Component: def build(self) -> Component:
if self.content is None: if self.content is None:
content = Spacer() content = Spacer()

View File

@ -1,16 +1,19 @@
from rio import Column, Component, event, TextStyle, Text, Spacer, Revealer, SwitcherBar, SwitcherBarChangeEvent from typing import Optional
from rio import Column, Component, event, TextStyle, Text, Spacer, Revealer, SwitcherBar, SwitcherBarChangeEvent, ProgressCircle
from src.ez_lan_manager import ConfigurationService, CateringService from src.ez_lan_manager import ConfigurationService, CateringService
from src.ez_lan_manager.components.CateringSelectionItem import CateringSelectionItem from src.ez_lan_manager.components.CateringSelectionItem import CateringSelectionItem
from src.ez_lan_manager.components.MainViewContentBox import MainViewContentBox from src.ez_lan_manager.components.MainViewContentBox import MainViewContentBox
from src.ez_lan_manager.components.ShoppingCartAndOrders import ShoppingCartAndOrders from src.ez_lan_manager.components.ShoppingCartAndOrders import ShoppingCartAndOrders
from src.ez_lan_manager.pages import BasePage from src.ez_lan_manager.pages import BasePage
from src.ez_lan_manager.types.CateringMenuItem import CateringMenuItemCategory from src.ez_lan_manager.types.CateringMenuItem import CateringMenuItemCategory, CateringMenuItem
from src.ez_lan_manager.types.SessionStorage import SessionStorage from src.ez_lan_manager.types.SessionStorage import SessionStorage
class CateringPage(Component): class CateringPage(Component):
show_cart = True show_cart = True
all_menu_items: Optional[list[CateringMenuItem]] = None
def __post_init__(self) -> None: def __post_init__(self) -> None:
self.session[SessionStorage].subscribe_to_logged_in_or_out_event(self.__class__.__name__, self.on_user_logged_in_status_changed) self.session[SessionStorage].subscribe_to_logged_in_or_out_event(self.__class__.__name__, self.on_user_logged_in_status_changed)
@ -18,6 +21,8 @@ class CateringPage(Component):
@event.on_populate @event.on_populate
async def on_populate(self) -> None: async def on_populate(self) -> None:
await self.session.set_title(f"{self.session[ConfigurationService].get_lan_info().name} - Catering") await self.session.set_title(f"{self.session[ConfigurationService].get_lan_info().name} - Catering")
self.all_menu_items = await self.session[CateringService].get_menu()
async def on_user_logged_in_status_changed(self) -> None: async def on_user_logged_in_status_changed(self) -> None:
await self.force_refresh() await self.force_refresh()
@ -25,9 +30,13 @@ class CateringPage(Component):
async def on_switcher_bar_changed(self, _: SwitcherBarChangeEvent) -> None: async def on_switcher_bar_changed(self, _: SwitcherBarChangeEvent) -> None:
await self.shopping_cart_and_orders.switch() await self.shopping_cart_and_orders.switch()
@staticmethod
def get_menu_items_by_category(all_menu_items: list[CateringMenuItem], category: Optional[CateringMenuItemCategory]) -> list[CateringMenuItem]:
return list(filter(lambda item: item.category == category, all_menu_items))
def build(self) -> Component: def build(self) -> Component:
user_id = self.session[SessionStorage].user_id user_id = self.session[SessionStorage].user_id
catering_service = self.session[CateringService]
self.shopping_cart_and_orders = ShoppingCartAndOrders() self.shopping_cart_and_orders = ShoppingCartAndOrders()
switcher_bar = SwitcherBar( switcher_bar = SwitcherBar(
values=["cart", "orders"], values=["cart", "orders"],
@ -58,12 +67,14 @@ class CateringPage(Component):
) )
) if user_id else Spacer() ) if user_id else Spacer()
return BasePage( menu = [MainViewContentBox(
content=Column( ProgressCircle(
# SHOPPING CART color="secondary",
shopping_cart_and_orders_container, align_x=0.5,
# ITEM SELECTION margin_top=2,
MainViewContentBox( margin_bottom=2
)
)] if not self.all_menu_items else [MainViewContentBox(
Revealer( Revealer(
header="Snacks", header="Snacks",
content=Column( content=Column(
@ -75,7 +86,7 @@ class CateringPage(Component):
is_sensitive=(user_id is not None) and not catering_menu_item.is_disabled, is_sensitive=(user_id is not None) and not catering_menu_item.is_disabled,
additional_info=catering_menu_item.additional_info, additional_info=catering_menu_item.additional_info,
is_grey=idx % 2 == 0 is_grey=idx % 2 == 0
) for idx, catering_menu_item in enumerate(catering_service.get_menu(CateringMenuItemCategory.SNACK))], ) for idx, catering_menu_item in enumerate(self.get_menu_items_by_category(self.all_menu_items, CateringMenuItemCategory.SNACK))],
), ),
header_style=TextStyle( header_style=TextStyle(
fill=self.session.theme.background_color, fill=self.session.theme.background_color,
@ -97,7 +108,7 @@ class CateringPage(Component):
is_sensitive=(user_id is not None) and not catering_menu_item.is_disabled, is_sensitive=(user_id is not None) and not catering_menu_item.is_disabled,
additional_info=catering_menu_item.additional_info, additional_info=catering_menu_item.additional_info,
is_grey=idx % 2 == 0 is_grey=idx % 2 == 0
) for idx, catering_menu_item in enumerate(catering_service.get_menu(CateringMenuItemCategory.BREAKFAST))], ) for idx, catering_menu_item in enumerate(self.get_menu_items_by_category(self.all_menu_items, CateringMenuItemCategory.BREAKFAST))],
), ),
header_style=TextStyle( header_style=TextStyle(
fill=self.session.theme.background_color, fill=self.session.theme.background_color,
@ -119,7 +130,7 @@ class CateringPage(Component):
is_sensitive=(user_id is not None) and not catering_menu_item.is_disabled, is_sensitive=(user_id is not None) and not catering_menu_item.is_disabled,
additional_info=catering_menu_item.additional_info, additional_info=catering_menu_item.additional_info,
is_grey=idx % 2 == 0 is_grey=idx % 2 == 0
) for idx, catering_menu_item in enumerate(catering_service.get_menu(CateringMenuItemCategory.MAIN_COURSE))], ) for idx, catering_menu_item in enumerate(self.get_menu_items_by_category(self.all_menu_items, CateringMenuItemCategory.MAIN_COURSE))],
), ),
header_style=TextStyle( header_style=TextStyle(
fill=self.session.theme.background_color, fill=self.session.theme.background_color,
@ -141,7 +152,7 @@ class CateringPage(Component):
is_sensitive=(user_id is not None) and not catering_menu_item.is_disabled, is_sensitive=(user_id is not None) and not catering_menu_item.is_disabled,
additional_info=catering_menu_item.additional_info, additional_info=catering_menu_item.additional_info,
is_grey=idx % 2 == 0 is_grey=idx % 2 == 0
) for idx, catering_menu_item in enumerate(catering_service.get_menu(CateringMenuItemCategory.DESSERT))], ) for idx, catering_menu_item in enumerate(self.get_menu_items_by_category(self.all_menu_items, CateringMenuItemCategory.DESSERT))],
), ),
header_style=TextStyle( header_style=TextStyle(
fill=self.session.theme.background_color, fill=self.session.theme.background_color,
@ -163,7 +174,7 @@ class CateringPage(Component):
is_sensitive=(user_id is not None) and not catering_menu_item.is_disabled, is_sensitive=(user_id is not None) and not catering_menu_item.is_disabled,
additional_info=catering_menu_item.additional_info, additional_info=catering_menu_item.additional_info,
is_grey=idx % 2 == 0 is_grey=idx % 2 == 0
) for idx, catering_menu_item in enumerate(catering_service.get_menu(CateringMenuItemCategory.BEVERAGE_NON_ALCOHOLIC))], ) for idx, catering_menu_item in enumerate(self.get_menu_items_by_category(self.all_menu_items, CateringMenuItemCategory.BEVERAGE_NON_ALCOHOLIC))],
), ),
header_style=TextStyle( header_style=TextStyle(
fill=self.session.theme.background_color, fill=self.session.theme.background_color,
@ -185,7 +196,7 @@ class CateringPage(Component):
is_sensitive=(user_id is not None) and not catering_menu_item.is_disabled, is_sensitive=(user_id is not None) and not catering_menu_item.is_disabled,
additional_info=catering_menu_item.additional_info, additional_info=catering_menu_item.additional_info,
is_grey=idx % 2 == 0 is_grey=idx % 2 == 0
) for idx, catering_menu_item in enumerate(catering_service.get_menu(CateringMenuItemCategory.BEVERAGE_ALCOHOLIC))], ) for idx, catering_menu_item in enumerate(self.get_menu_items_by_category(self.all_menu_items, CateringMenuItemCategory.BEVERAGE_ALCOHOLIC))],
), ),
header_style=TextStyle( header_style=TextStyle(
fill=self.session.theme.background_color, fill=self.session.theme.background_color,
@ -207,7 +218,7 @@ class CateringPage(Component):
is_sensitive=(user_id is not None) and not catering_menu_item.is_disabled, is_sensitive=(user_id is not None) and not catering_menu_item.is_disabled,
additional_info=catering_menu_item.additional_info, additional_info=catering_menu_item.additional_info,
is_grey=idx % 2 == 0 is_grey=idx % 2 == 0
) for idx, catering_menu_item in enumerate(catering_service.get_menu(CateringMenuItemCategory.BEVERAGE_COCKTAIL))], ) for idx, catering_menu_item in enumerate(self.get_menu_items_by_category(self.all_menu_items, CateringMenuItemCategory.BEVERAGE_COCKTAIL))],
), ),
header_style=TextStyle( header_style=TextStyle(
fill=self.session.theme.background_color, fill=self.session.theme.background_color,
@ -229,7 +240,7 @@ class CateringPage(Component):
is_sensitive=(user_id is not None) and not catering_menu_item.is_disabled, is_sensitive=(user_id is not None) and not catering_menu_item.is_disabled,
additional_info=catering_menu_item.additional_info, additional_info=catering_menu_item.additional_info,
is_grey=idx % 2 == 0 is_grey=idx % 2 == 0
) for idx, catering_menu_item in enumerate(catering_service.get_menu(CateringMenuItemCategory.BEVERAGE_SHOT))], ) for idx, catering_menu_item in enumerate(self.get_menu_items_by_category(self.all_menu_items, CateringMenuItemCategory.BEVERAGE_SHOT))],
), ),
header_style=TextStyle( header_style=TextStyle(
fill=self.session.theme.background_color, fill=self.session.theme.background_color,
@ -251,7 +262,7 @@ class CateringPage(Component):
is_sensitive=(user_id is not None) and not catering_menu_item.is_disabled, is_sensitive=(user_id is not None) and not catering_menu_item.is_disabled,
additional_info=catering_menu_item.additional_info, additional_info=catering_menu_item.additional_info,
is_grey=idx % 2 == 0 is_grey=idx % 2 == 0
) for idx, catering_menu_item in enumerate(catering_service.get_menu(CateringMenuItemCategory.NON_FOOD))], ) for idx, catering_menu_item in enumerate(self.get_menu_items_by_category(self.all_menu_items, CateringMenuItemCategory.NON_FOOD))],
), ),
header_style=TextStyle( header_style=TextStyle(
fill=self.session.theme.background_color, fill=self.session.theme.background_color,
@ -260,7 +271,14 @@ class CateringPage(Component):
margin=1, margin=1,
align_y=0.5 align_y=0.5
) )
), )]
return BasePage(
content=Column(
# SHOPPING CART
shopping_cart_and_orders_container,
# ITEM SELECTION
*menu,
align_y=0 align_y=0
) )
) )

View File

@ -1,4 +1,5 @@
from datetime import datetime, timedelta from datetime import datetime, timedelta
from typing import Optional
from rio import Text, Column, TextStyle, Component, event, TextInput, MultiLineTextInput, Row, Button from rio import Text, Column, TextStyle, Component, event, TextInput, MultiLineTextInput, Row, Button
@ -7,6 +8,7 @@ from src.ez_lan_manager.components.AnimatedText import AnimatedText
from src.ez_lan_manager.components.MainViewContentBox import MainViewContentBox from src.ez_lan_manager.components.MainViewContentBox import MainViewContentBox
from src.ez_lan_manager.pages import BasePage from src.ez_lan_manager.pages import BasePage
from src.ez_lan_manager.types.SessionStorage import SessionStorage from src.ez_lan_manager.types.SessionStorage import SessionStorage
from src.ez_lan_manager.types.User import User
class ContactPage(Component): class ContactPage(Component):
@ -14,10 +16,15 @@ class ContactPage(Component):
# Using list to bypass this behavior # Using list to bypass this behavior
last_message_sent: list[datetime] = [datetime(day=1, month=1, year=2000)] last_message_sent: list[datetime] = [datetime(day=1, month=1, year=2000)]
display_printing: list[bool] = [False] display_printing: list[bool] = [False]
user: Optional[User] = None
@event.on_populate @event.on_populate
async def on_populate(self) -> None: async def on_populate(self) -> None:
await self.session.set_title(f"{self.session[ConfigurationService].get_lan_info().name} - Kontakt") await self.session.set_title(f"{self.session[ConfigurationService].get_lan_info().name} - Kontakt")
if self.session[SessionStorage].user_id is not None:
self.user = await self.session[UserService].get_user(self.session[SessionStorage].user_id)
else:
self.user = None
async def on_send_pressed(self) -> None: async def on_send_pressed(self) -> None:
error_msg = "" error_msg = ""
@ -51,11 +58,6 @@ class ContactPage(Component):
await self.animated_text.display_text(True, "Nachricht erfolgreich gesendet!") await self.animated_text.display_text(True, "Nachricht erfolgreich gesendet!")
def build(self) -> Component: def build(self) -> Component:
if self.session[SessionStorage].user_id is not None:
user = self.session[UserService].get_user(self.session[SessionStorage].user_id)
else:
user = None
self.animated_text = AnimatedText( self.animated_text = AnimatedText(
margin_top = 2, margin_top = 2,
margin_bottom = 1, margin_bottom = 1,
@ -64,7 +66,7 @@ class ContactPage(Component):
self.email_input = TextInput( self.email_input = TextInput(
label="E-Mail Adresse", label="E-Mail Adresse",
text="" if not user else user.user_mail, text="" if not self.user else self.user.user_mail,
margin_left=1, margin_left=1,
margin_right=1, margin_right=1,
margin_bottom=1, margin_bottom=1,

View File

@ -15,12 +15,12 @@ class DbErrorPage(Component):
async def on_window_size_change(self) -> None: async def on_window_size_change(self) -> None:
await self.force_refresh() await self.force_refresh()
@event.on_mount # @event.on_mount
async def retry_db_connect(self) -> None: # async def retry_db_connect(self) -> None:
await self.session.set_title(f"{self.session[ConfigurationService].get_lan_info().name} - Fehler") # await self.session.set_title(f"{self.session[ConfigurationService].get_lan_info().name} - Fehler")
while not self.session[DatabaseService].is_connected: # while not self.session[DatabaseService].is_connected:
await sleep(2) # await sleep(2)
self.session.navigate_to("./") # self.session.navigate_to("./")
def build(self) -> Component: def build(self) -> Component:
content = Card( content = Card(

View File

@ -3,7 +3,8 @@ from hashlib import sha256
from typing import Optional from typing import Optional
from from_root import from_root from from_root import from_root
from rio import Column, Component, event, Text, TextStyle, Button, Color, Row, TextInput, Image, TextInputChangeEvent, NoFileSelectedError from rio import Column, Component, event, Text, TextStyle, Button, Color, Row, TextInput, Image, TextInputChangeEvent, NoFileSelectedError, \
ProgressCircle
from email_validator import validate_email, EmailNotValidError from email_validator import validate_email, EmailNotValidError
from src.ez_lan_manager import ConfigurationService, UserService from src.ez_lan_manager import ConfigurationService, UserService
@ -15,6 +16,8 @@ from src.ez_lan_manager.types.User import User
class EditProfilePage(Component): class EditProfilePage(Component):
user: Optional[User] = None
pfp: Optional[bytes] = None
@staticmethod @staticmethod
def optional_date_to_str(d: Optional[date]) -> str: def optional_date_to_str(d: Optional[date]) -> str:
if not d: if not d:
@ -24,6 +27,8 @@ class EditProfilePage(Component):
@event.on_populate @event.on_populate
async def on_populate(self) -> None: async def on_populate(self) -> None:
await self.session.set_title(f"{self.session[ConfigurationService].get_lan_info().name} - Profil bearbeiten") await self.session.set_title(f"{self.session[ConfigurationService].get_lan_info().name} - Profil bearbeiten")
self.user = await self.session[UserService].get_user(self.session[SessionStorage].user_id)
self.pfp = await self.session[UserService].get_profile_picture(self.user.user_id)
def on_email_changed(self, change_event: TextInputChangeEvent) -> None: def on_email_changed(self, change_event: TextInputChangeEvent) -> None:
try: try:
@ -58,7 +63,7 @@ class EditProfilePage(Component):
return return
image_data = await new_pfp.read_bytes() image_data = await new_pfp.read_bytes()
self.session[UserService].set_profile_picture(self.session[SessionStorage].user_id, image_data) await self.session[UserService].set_profile_picture(self.session[SessionStorage].user_id, image_data)
self.pfp_image_container.image = image_data self.pfp_image_container.image = image_data
await self.animated_text.display_text(True, "Gespeichert!") await self.animated_text.display_text(True, "Gespeichert!")
@ -72,7 +77,7 @@ class EditProfilePage(Component):
await self.animated_text.display_text(False, "Passwörter nicht gleich!") await self.animated_text.display_text(False, "Passwörter nicht gleich!")
return return
user: User = self.session[UserService].get_user(self.session[SessionStorage].user_id) user: User = await self.session[UserService].get_user(self.session[SessionStorage].user_id)
user.user_mail = self.email_input.text user.user_mail = self.email_input.text
if len(self.birthday_input.text) == 0: if len(self.birthday_input.text) == 0:
@ -86,12 +91,24 @@ class EditProfilePage(Component):
if len(self.new_pw_1_input.text.strip()) > 0: if len(self.new_pw_1_input.text.strip()) > 0:
user.user_password = sha256(self.new_pw_1_input.text.encode(encoding="utf-8")).hexdigest() user.user_password = sha256(self.new_pw_1_input.text.encode(encoding="utf-8")).hexdigest()
self.session[UserService].update_user(user) await self.session[UserService].update_user(user)
await self.animated_text.display_text(True, "Gespeichert!") await self.animated_text.display_text(True, "Gespeichert!")
def build(self) -> Component: def build(self) -> Component:
user = self.session[UserService].get_user(self.session[SessionStorage].user_id) if not self.user:
pfp = self.session[UserService].get_profile_picture(user.user_id) return BasePage(
content=Column(
MainViewContentBox(
ProgressCircle(
color="secondary",
align_x=0.5,
margin_top=2,
margin_bottom=2
)
),
align_y = 0
)
)
self.animated_text = AnimatedText( self.animated_text = AnimatedText(
margin_top=2, margin_top=2,
@ -101,7 +118,7 @@ class EditProfilePage(Component):
self.email_input = TextInput( self.email_input = TextInput(
label="E-Mail Adresse", label="E-Mail Adresse",
text=user.user_mail, text=self.user.user_mail,
margin_left=1, margin_left=1,
margin_right=1, margin_right=1,
margin_bottom=1, margin_bottom=1,
@ -110,20 +127,20 @@ class EditProfilePage(Component):
) )
self.first_name_input = TextInput( self.first_name_input = TextInput(
label="Vorname", label="Vorname",
text=user.user_first_name, text=self.user.user_first_name,
margin_left=1, margin_left=1,
margin_right=1, margin_right=1,
grow_x=True grow_x=True
) )
self.last_name_input = TextInput( self.last_name_input = TextInput(
label="Nachname", label="Nachname",
text=user.user_last_name, text=self.user.user_last_name,
margin_right=1, margin_right=1,
grow_x=True grow_x=True
) )
self.birthday_input = TextInput( self.birthday_input = TextInput(
label="Geburtstag (TT.MM.JJJJ)", label="Geburtstag (TT.MM.JJJJ)",
text=self.optional_date_to_str(user.user_birth_day), text=self.optional_date_to_str(self.user.user_birth_day),
margin_left=1, margin_left=1,
margin_right=1, margin_right=1,
margin_bottom=1, margin_bottom=1,
@ -150,7 +167,7 @@ class EditProfilePage(Component):
) )
self.pfp_image_container = Image( self.pfp_image_container = Image(
from_root("src/ez_lan_manager/assets/img/anon_pfp.png") if pfp is None else pfp, from_root("src/ez_lan_manager/assets/img/anon_pfp.png") if self.pfp is None else self.pfp,
align_x=0.5, align_x=0.5,
min_width=10, min_width=10,
min_height=10, min_height=10,
@ -176,8 +193,8 @@ class EditProfilePage(Component):
on_press=self.upload_new_pfp on_press=self.upload_new_pfp
), ),
Row( Row(
TextInput(label="Deine User-ID", text=user.user_id, is_sensitive=False, margin_left=1, grow_x=False), TextInput(label="Deine User-ID", text=self.user.user_id, is_sensitive=False, margin_left=1, grow_x=False),
TextInput(label="Dein Nickname", text=user.user_name, is_sensitive=False, margin_left=1, margin_right=1, grow_x=True), TextInput(label="Dein Nickname", text=self.user.user_name, is_sensitive=False, margin_left=1, margin_right=1, grow_x=True),
margin_bottom=1 margin_bottom=1
), ),
self.email_input, self.email_input,

View File

@ -24,11 +24,11 @@ class ForgotPasswordPage(Component):
lan_info = self.session[ConfigurationService].get_lan_info() lan_info = self.session[ConfigurationService].get_lan_info()
user_service = self.session[UserService] user_service = self.session[UserService]
mailing_service = self.session[MailingService] mailing_service = self.session[MailingService]
user = user_service.get_user(self.email_input.text.strip()) user = await user_service.get_user(self.email_input.text.strip())
if user is not None: if user is not None:
new_password = "".join(choices(user_service.ALLOWED_USER_NAME_SYMBOLS, k=16)) new_password = "".join(choices(user_service.ALLOWED_USER_NAME_SYMBOLS, k=16))
user.user_password = sha256(new_password.encode(encoding="utf-8")).hexdigest() user.user_password = sha256(new_password.encode(encoding="utf-8")).hexdigest()
user_service.update_user(user) await user_service.update_user(user)
await mailing_service.send_email( await mailing_service.send_email(
subject=f"Dein neues Passwort für {lan_info.name}", subject=f"Dein neues Passwort für {lan_info.name}",
body=f"Du hast für den EZ-LAN Manager der {lan_info.name} ein neues Passwort angefragt. " body=f"Du hast für den EZ-LAN Manager der {lan_info.name} ein neues Passwort angefragt. "

View File

@ -5,37 +5,50 @@ from rio import Column, Component, event, TextStyle, Text, Button, Row, TextInpu
from src.ez_lan_manager import ConfigurationService, UserService, TicketingService, SeatingService from src.ez_lan_manager import ConfigurationService, UserService, TicketingService, SeatingService
from src.ez_lan_manager.components.MainViewContentBox import MainViewContentBox from src.ez_lan_manager.components.MainViewContentBox import MainViewContentBox
from src.ez_lan_manager.pages import BasePage from src.ez_lan_manager.pages import BasePage
from src.ez_lan_manager.types.Seat import Seat
from src.ez_lan_manager.types.User import User from src.ez_lan_manager.types.User import User
class GuestsPage(Component): class GuestsPage(Component):
table_elements: list[Button] = [] table_elements: list[Button] = []
users_with_tickets: list[User] = [] users_with_tickets: list[User] = []
users_with_seats: dict[User, Seat] = {}
user_filter: Optional[str] = None user_filter: Optional[str] = None
def __post_init__(self) -> None:
user_service = self.session[UserService]
all_users = user_service.get_all_users()
ticketing_service = self.session[TicketingService]
self.users_with_tickets = list(filter(lambda user: ticketing_service.get_user_ticket(user.user_id) is not None, all_users))
@event.on_populate @event.on_populate
async def on_populate(self) -> None: async def on_populate(self) -> None:
await self.session.set_title(f"{self.session[ConfigurationService].get_lan_info().name} - Teilnehmer") await self.session.set_title(f"{self.session[ConfigurationService].get_lan_info().name} - Teilnehmer")
user_service = self.session[UserService]
all_users = await user_service.get_all_users()
ticketing_service = self.session[TicketingService]
seating_service = self.session[SeatingService]
u_w_t = []
u_w_s = {}
for user in all_users:
ticket = await ticketing_service.get_user_ticket(user.user_id)
seat = await seating_service.get_user_seat(user.user_id)
if ticket is not None:
u_w_t.append(user)
if seat is not None:
u_w_s[user] = seat
self.users_with_tickets = u_w_t
self.users_with_seats = u_w_s
def on_searchbar_content_change(self, change_event: TextInputChangeEvent) -> None: def on_searchbar_content_change(self, change_event: TextInputChangeEvent) -> None:
self.user_filter = change_event.text self.user_filter = change_event.text
def build(self) -> Component: def build(self) -> Component:
seating_service = self.session[SeatingService]
if self.user_filter: if self.user_filter:
users = [user for user in self.users_with_tickets if self.user_filter.lower() in user.user_name or self.user_filter.lower() in str(user.user_id)] users = [user for user in self.users_with_tickets if self.user_filter.lower() in user.user_name or self.user_filter.lower() in str(user.user_id)]
else: else:
users = self.users_with_tickets users = self.users_with_tickets
self.table_elements.clear() self.table_elements.clear()
for idx, user in enumerate(users): for idx, user in enumerate(users):
seat = seating_service.get_user_seat(user.user_id) try:
seat = self.users_with_seats[user]
except KeyError:
seat = None
self.table_elements.append( self.table_elements.append(
Button( Button(
content=Row(Text(text=f"{user.user_id:0>4}", align_x=0, margin_right=1), Text(text=user.user_name, grow_x=True, wrap="ellipsize"), Text(text="-" if seat is None else seat.seat_id, align_x=1)), content=Row(Text(text=f"{user.user_id:0>4}", align_x=0, margin_right=1), Text(text=user.user_name, grow_x=True, wrap="ellipsize"), Text(text="-" if seat is None else seat.seat_id, align_x=1)),

View File

@ -12,7 +12,7 @@ class NewsPage(Component):
@event.on_populate @event.on_populate
async def on_populate(self) -> None: async def on_populate(self) -> None:
await self.session.set_title(f"{self.session[ConfigurationService].get_lan_info().name} - Neuigkeiten") await self.session.set_title(f"{self.session[ConfigurationService].get_lan_info().name} - Neuigkeiten")
self.news_posts = self.session[NewsService].get_news()[:8] self.news_posts = (await self.session[NewsService].get_news())[:8]
def build(self) -> Component: def build(self) -> Component:
posts = [NewsPost( posts = [NewsPost(

View File

@ -62,13 +62,13 @@ class RegisterPage(Component):
mailing_service = self.session[MailingService] mailing_service = self.session[MailingService]
lan_info = self.session[ConfigurationService].get_lan_info() lan_info = self.session[ConfigurationService].get_lan_info()
if user_service.get_user(self.email_input.text) is not None or user_service.get_user(self.user_name_input.text) is not None: if await user_service.get_user(self.email_input.text) is not None or await user_service.get_user(self.user_name_input.text) is not None:
await self.animated_text.display_text(False, "Benutzername oder E-Mail bereits regestriert!") await self.animated_text.display_text(False, "Benutzername oder E-Mail bereits regestriert!")
self.submit_button.is_loading = False self.submit_button.is_loading = False
return return
try: try:
new_user = user_service.create_user(self.user_name_input.text, self.email_input.text, self.pw_1.text) new_user = await user_service.create_user(self.user_name_input.text, self.email_input.text, self.pw_1.text)
if not new_user: if not new_user:
raise RuntimeError("User could not be created") raise RuntimeError("User could not be created")
except Exception as e: except Exception as e:

View File

@ -13,8 +13,8 @@ class AccountingService:
def __init__(self, db_service: DatabaseService) -> None: def __init__(self, db_service: DatabaseService) -> None:
self._db_service = db_service self._db_service = db_service
def add_balance(self, user_id: int, balance_to_add: int, reference: str) -> int: async def add_balance(self, user_id: int, balance_to_add: int, reference: str) -> int:
self._db_service.add_transaction(Transaction( await self._db_service.add_transaction(Transaction(
user_id=user_id, user_id=user_id,
value=balance_to_add, value=balance_to_add,
is_debit=False, is_debit=False,
@ -22,13 +22,13 @@ class AccountingService:
transaction_date=datetime.now() transaction_date=datetime.now()
)) ))
logger.debug(f"Added balance of {self.make_euro_string_from_int(balance_to_add)} to user with ID {user_id}") logger.debug(f"Added balance of {self.make_euro_string_from_int(balance_to_add)} to user with ID {user_id}")
return self.get_balance(user_id) return await self.get_balance(user_id)
def remove_balance(self, user_id: int, balance_to_remove: int, reference: str) -> int: async def remove_balance(self, user_id: int, balance_to_remove: int, reference: str) -> int:
current_balance = self.get_balance(user_id) current_balance = await self.get_balance(user_id)
if (current_balance - balance_to_remove) < 0: if (current_balance - balance_to_remove) < 0:
raise InsufficientFundsError raise InsufficientFundsError
self._db_service.add_transaction(Transaction( await self._db_service.add_transaction(Transaction(
user_id=user_id, user_id=user_id,
value=balance_to_remove, value=balance_to_remove,
is_debit=True, is_debit=True,
@ -36,19 +36,19 @@ class AccountingService:
transaction_date=datetime.now() transaction_date=datetime.now()
)) ))
logger.debug(f"Removed balance of {self.make_euro_string_from_int(balance_to_remove)} to user with ID {user_id}") logger.debug(f"Removed balance of {self.make_euro_string_from_int(balance_to_remove)} to user with ID {user_id}")
return self.get_balance(user_id) return await self.get_balance(user_id)
def get_balance(self, user_id: int) -> int: async def get_balance(self, user_id: int) -> int:
balance_buffer = 0 balance_buffer = 0
for transaction in self._db_service.get_all_transactions_for_user(user_id): for transaction in await self._db_service.get_all_transactions_for_user(user_id):
if transaction.is_debit: if transaction.is_debit:
balance_buffer -= transaction.value balance_buffer -= transaction.value
else: else:
balance_buffer += transaction.value balance_buffer += transaction.value
return balance_buffer return balance_buffer
def get_transaction_history(self, user_id: int) -> list[Transaction]: async def get_transaction_history(self, user_id: int) -> list[Transaction]:
return self._db_service.get_all_transactions_for_user(user_id) return await self._db_service.get_all_transactions_for_user(user_id)
@staticmethod @staticmethod
def make_euro_string_from_int(cent_int: int) -> str: def make_euro_string_from_int(cent_int: int) -> str:

View File

@ -23,93 +23,93 @@ class CateringService:
# ORDERS # ORDERS
def place_order(self, menu_items: CateringMenuItemsWithAmount, user_id: int, is_delivery: bool = True) -> CateringOrder: async def place_order(self, menu_items: CateringMenuItemsWithAmount, user_id: int, is_delivery: bool = True) -> CateringOrder:
for menu_item in menu_items: for menu_item in menu_items:
if menu_item.is_disabled: if menu_item.is_disabled:
raise CateringError("Order includes disabled items") raise CateringError("Order includes disabled items")
user = self._user_service.get_user(user_id) user = await self._user_service.get_user(user_id)
if not user: if not user:
raise CateringError("User does not exist") raise CateringError("User does not exist")
total_price = sum([item.price * quantity for item, quantity in menu_items.items()]) total_price = sum([item.price * quantity for item, quantity in menu_items.items()])
if self._accounting_service.get_balance(user_id) < total_price: if await self._accounting_service.get_balance(user_id) < total_price:
raise CateringError("Insufficient funds") raise CateringError("Insufficient funds")
order = self._db_service.add_new_order(menu_items, user_id, is_delivery) order = await self._db_service.add_new_order(menu_items, user_id, is_delivery)
if order: if order:
self._accounting_service.remove_balance(user_id, total_price, f"CATERING - {order.order_id}") await self._accounting_service.remove_balance(user_id, total_price, f"CATERING - {order.order_id}")
logger.info(f"User '{order.customer.user_name}' (ID:{order.customer.user_id}) ordered from catering for {self._accounting_service.make_euro_string_from_int(total_price)}") logger.info(f"User '{order.customer.user_name}' (ID:{order.customer.user_id}) ordered from catering for {self._accounting_service.make_euro_string_from_int(total_price)}")
return order return order
def update_order_status(self, order_id: int, new_status: CateringOrderStatus) -> bool: async def update_order_status(self, order_id: int, new_status: CateringOrderStatus) -> bool:
if new_status == CateringOrderStatus.CANCELED: if new_status == CateringOrderStatus.CANCELED:
# Cancelled orders need to be refunded # Cancelled orders need to be refunded
raise CateringError("Orders cannot be canceled this way, use CateringService.cancel_order") raise CateringError("Orders cannot be canceled this way, use CateringService.cancel_order")
return self._db_service.change_order_status(order_id, new_status) return await self._db_service.change_order_status(order_id, new_status)
def get_orders(self) -> list[CateringOrder]: async def get_orders(self) -> list[CateringOrder]:
return self._db_service.get_orders() return await self._db_service.get_orders()
def get_orders_for_user(self, user_id: int) -> list[CateringOrder]: async def get_orders_for_user(self, user_id: int) -> list[CateringOrder]:
return self._db_service.get_orders(user_id=user_id) return await self._db_service.get_orders(user_id=user_id)
def get_orders_by_status(self, status: CateringOrderStatus) -> list[CateringOrder]: async def get_orders_by_status(self, status: CateringOrderStatus) -> list[CateringOrder]:
return self._db_service.get_orders(status=status) return await self._db_service.get_orders(status=status)
def cancel_order(self, order: CateringOrder) -> bool: async def cancel_order(self, order: CateringOrder) -> bool:
if self._db_service.change_order_status(order.order_id, CateringOrderStatus.CANCELED): if self._db_service.change_order_status(order.order_id, CateringOrderStatus.CANCELED):
self._accounting_service.add_balance(order.customer.user_id, order.price, f"CATERING REFUND - {order.order_id}") await self._accounting_service.add_balance(order.customer.user_id, order.price, f"CATERING REFUND - {order.order_id}")
return True return True
return False return False
# MENU ITEMS # MENU ITEMS
def get_menu(self, category: Optional[CateringMenuItemCategory] = None) -> list[CateringMenuItem]: async def get_menu(self, category: Optional[CateringMenuItemCategory] = None) -> list[CateringMenuItem]:
items = self._db_service.get_menu_items() items = await self._db_service.get_menu_items()
if not category: if not category:
return items return items
return list(filter(lambda item: item.category == category, items)) return list(filter(lambda item: item.category == category, items))
def get_menu_item_by_id(self, menu_item_id: int) -> CateringMenuItem: async def get_menu_item_by_id(self, menu_item_id: int) -> CateringMenuItem:
item = self._db_service.get_menu_item(menu_item_id) item = await self._db_service.get_menu_item(menu_item_id)
if not item: if not item:
raise CateringError("Menu item not found") raise CateringError("Menu item not found")
return item return item
def add_menu_item(self, name: str, info: str, price: int, category: CateringMenuItemCategory, is_disabled: bool = False) -> CateringMenuItem: async def add_menu_item(self, name: str, info: str, price: int, category: CateringMenuItemCategory, is_disabled: bool = False) -> CateringMenuItem:
if new_item := self._db_service.add_menu_item(name, info, price, category, is_disabled): if new_item := await self._db_service.add_menu_item(name, info, price, category, is_disabled):
return new_item return new_item
raise CateringError(f"Could not add item '{name}' to the menu.") raise CateringError(f"Could not add item '{name}' to the menu.")
def remove_menu_item(self, menu_item_id: int) -> bool: async def remove_menu_item(self, menu_item_id: int) -> bool:
return self._db_service.delete_menu_item(menu_item_id) return await self._db_service.delete_menu_item(menu_item_id)
def change_menu_item(self, updated_item: CateringMenuItem) -> bool: async def change_menu_item(self, updated_item: CateringMenuItem) -> bool:
return self._db_service.update_menu_item(updated_item) return await self._db_service.update_menu_item(updated_item)
def disable_menu_item(self, menu_item_id: int) -> bool: async def disable_menu_item(self, menu_item_id: int) -> bool:
try: try:
item = self.get_menu_item_by_id(menu_item_id) item = await self.get_menu_item_by_id(menu_item_id)
except CateringError: except CateringError:
return False return False
item.is_disabled = True item.is_disabled = True
return self._db_service.update_menu_item(item) return await self._db_service.update_menu_item(item)
def enable_menu_item(self, menu_item_id: int) -> bool: async def enable_menu_item(self, menu_item_id: int) -> bool:
try: try:
item = self.get_menu_item_by_id(menu_item_id) item = await self.get_menu_item_by_id(menu_item_id)
except CateringError: except CateringError:
return False return False
item.is_disabled = False item.is_disabled = False
return self._db_service.update_menu_item(item) return await self._db_service.update_menu_item(item)
def disable_menu_items_by_category(self, category: CateringMenuItemCategory) -> bool: async def disable_menu_items_by_category(self, category: CateringMenuItemCategory) -> bool:
items = self.get_menu(category=category) items = await self.get_menu(category=category)
return all([self.disable_menu_item(item.item_id) for item in items]) return all([self.disable_menu_item(item.item_id) for item in items])
def enable_menu_items_by_category(self, category: CateringMenuItemCategory) -> bool: async def enable_menu_items_by_category(self, category: CateringMenuItemCategory) -> bool:
items = self.get_menu(category=category) items = await self.get_menu(category=category)
return all([self.enable_menu_item(item.item_id) for item in items]) return all([self.enable_menu_item(item.item_id) for item in items])
# CART # CART

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
import logging import logging
from datetime import date, datetime from datetime import date
from typing import Optional from typing import Optional
from src.ez_lan_manager.services.DatabaseService import DatabaseService from src.ez_lan_manager.services.DatabaseService import DatabaseService
@ -11,21 +11,22 @@ class NewsService:
def __init__(self, db_service: DatabaseService) -> None: def __init__(self, db_service: DatabaseService) -> None:
self._db_service = db_service self._db_service = db_service
def add_news(self, news: News) -> None: async def add_news(self, news: News) -> None:
if news.news_id is not None: if news.news_id is not None:
logger.warning("Can not add news with ID, ignoring...") logger.warning("Can not add news with ID, ignoring...")
return return
self._db_service.add_news(news) await self._db_service.add_news(news)
def get_news(self, dt_start: Optional[date] = None, dt_end: Optional[date] = None) -> list[News]: async def get_news(self, dt_start: Optional[date] = None, dt_end: Optional[date] = None) -> list[News]:
if not dt_end: if not dt_end:
dt_end = date.today() dt_end = date.today()
if not dt_start: if not dt_start:
dt_start = date(1900, 1, 1) dt_start = date(1900, 1, 1)
return self._db_service.get_news(dt_start, dt_end) return await self._db_service.get_news(dt_start, dt_end)
def get_latest_news(self) -> Optional[News]: async def get_latest_news(self) -> Optional[News]:
try: try:
return self.get_news(None, date.today())[0] all_news = await self.get_news(None, date.today())
return all_news[0]
except IndexError: except IndexError:
logger.debug("There are no news to fetch") logger.debug("There are no news to fetch")

View File

@ -36,27 +36,27 @@ class SeatingService:
ElementTree.parse(self._seating_configuration.base_svg_path).write(self._seating_plan, encoding="unicode") ElementTree.parse(self._seating_configuration.base_svg_path).write(self._seating_plan, encoding="unicode")
def get_seating(self) -> list[Seat]: async def get_seating(self) -> list[Seat]:
return self._db_service.get_seating_info() return await self._db_service.get_seating_info()
def get_seat(self, seat_id: str, cached_data: Optional[list[Seat]] = None) -> Optional[Seat]: async def get_seat(self, seat_id: str, cached_data: Optional[list[Seat]] = None) -> Optional[Seat]:
all_seats = self.get_seating() if not cached_data else cached_data all_seats = await self.get_seating() if not cached_data else cached_data
for seat in all_seats: for seat in all_seats:
if seat.seat_id == seat_id: if seat.seat_id == seat_id:
return seat return seat
def get_user_seat(self, user_id: int) -> Optional[Seat]: async def get_user_seat(self, user_id: int) -> Optional[Seat]:
all_seats = self.get_seating() all_seats = await self.get_seating()
for seat in all_seats: for seat in all_seats:
if seat.user and seat.user.user_id == user_id: if seat.user and seat.user.user_id == user_id:
return seat return seat
def seat_user(self, user_id: int, seat_id: str) -> None: async def seat_user(self, user_id: int, seat_id: str) -> None:
user_ticket = self._ticketing_service.get_user_ticket(user_id) user_ticket = await self._ticketing_service.get_user_ticket(user_id)
if not user_ticket: if not user_ticket:
raise NoTicketError raise NoTicketError
seat = self.get_seat(seat_id) seat = await self.get_seat(seat_id)
if not seat: if not seat:
raise SeatNotFoundError raise SeatNotFoundError
@ -66,10 +66,10 @@ class SeatingService:
if seat.user is not None: if seat.user is not None:
raise SeatAlreadyTakenError raise SeatAlreadyTakenError
self._db_service.seat_user(seat_id, user_id) await self._db_service.seat_user(seat_id, user_id)
self.update_svg_with_seating_status() await self.update_svg_with_seating_status()
def generate_new_seating_table(self, seating_plan_fp: Path, no_confirm: bool = False) -> None: async def generate_new_seating_table(self, seating_plan_fp: Path, no_confirm: bool = False) -> None:
if not no_confirm: if not no_confirm:
confirm = input("WARNING: THIS ACTION WILL DELETE ALL SEATING DATA! TYPE 'AGREE' TO CONTINUE: ") confirm = input("WARNING: THIS ACTION WILL DELETE ALL SEATING DATA! TYPE 'AGREE' TO CONTINUE: ")
if confirm != "AGREE": if confirm != "AGREE":
@ -95,10 +95,10 @@ class SeatingService:
except TypeError: except TypeError:
continue continue
self._db_service.generate_fresh_seats_table(sorted(seat_ids, key=lambda sd: sd[0])) await self._db_service.generate_fresh_seats_table(sorted(seat_ids, key=lambda sd: sd[0]))
self.update_svg_with_seating_status() await self.update_svg_with_seating_status()
def update_svg_with_seating_status(self) -> None: async def update_svg_with_seating_status(self) -> None:
et = ElementTree.parse(self._seating_configuration.base_svg_path) et = ElementTree.parse(self._seating_configuration.base_svg_path)
root = et.getroot() root = et.getroot()
namespace = {'svg': root.tag.split('}')[0].strip('{')} if '}' in root.tag else {} namespace = {'svg': root.tag.split('}')[0].strip('{')} if '}' in root.tag else {}
@ -113,13 +113,13 @@ class SeatingService:
rect_g_pairs.append((last_rect, elem)) rect_g_pairs.append((last_rect, elem))
last_rect = None last_rect = None
all_seats = self.get_seating() all_seats = await self.get_seating()
for rect, g in rect_g_pairs: for rect, g in rect_g_pairs:
seat_id = self.get_seat_id_from_element(g, namespace) seat_id = self.get_seat_id_from_element(g, namespace)
if not seat_id: if not seat_id:
continue continue
seat = self.get_seat(seat_id, cached_data=all_seats) seat = await self.get_seat(seat_id, cached_data=all_seats)
if not seat.is_blocked and seat.user is None: if not seat.is_blocked and seat.user is None:
rect.set("fill", "rgb(102, 255, 51)") rect.set("fill", "rgb(102, 255, 51)")
elif not seat.is_blocked and seat.user is not None: elif not seat.is_blocked and seat.user is not None:

View File

@ -21,30 +21,30 @@ class TicketingService:
self._db_service = db_service self._db_service = db_service
self._accounting_service = accounting_service self._accounting_service = accounting_service
def get_total_tickets(self) -> int: async def get_total_tickets(self) -> int:
return sum([self._lan_info.ticket_info.get_available_tickets(c) for c in self._lan_info.ticket_info.categories]) return sum([self._lan_info.ticket_info.get_available_tickets(c) for c in self._lan_info.ticket_info.categories])
def get_available_tickets(self) -> dict[str, int]: async def get_available_tickets(self) -> dict[str, int]:
result = self._lan_info.ticket_info.total_available_tickets result = self._lan_info.ticket_info.total_available_tickets
all_tickets = self._db_service.get_tickets() all_tickets = await self._db_service.get_tickets()
for ticket in all_tickets: for ticket in all_tickets:
result[ticket.category] -= 1 result[ticket.category] -= 1
return result return result
def purchase_ticket(self, user_id: int, category: str) -> Ticket: async def purchase_ticket(self, user_id: int, category: str) -> Ticket:
if category not in self._lan_info.ticket_info.categories or self.get_available_tickets()[category] < 1: if category not in self._lan_info.ticket_info.categories or (await self.get_available_tickets())[category] < 1:
raise TicketNotAvailableError(category) raise TicketNotAvailableError(category)
user_balance = self._accounting_service.get_balance(user_id) user_balance = await self._accounting_service.get_balance(user_id)
if self._lan_info.ticket_info.get_price(category) > user_balance: if self._lan_info.ticket_info.get_price(category) > user_balance:
raise InsufficientFundsError raise InsufficientFundsError
if self.get_user_ticket(user_id): if self.get_user_ticket(user_id):
raise UserAlreadyHasTicketError raise UserAlreadyHasTicketError
if new_ticket := self._db_service.generate_ticket_for_user(user_id, category): if new_ticket := await self._db_service.generate_ticket_for_user(user_id, category):
self._accounting_service.remove_balance( await self._accounting_service.remove_balance(
user_id, user_id,
self._lan_info.ticket_info.get_price(new_ticket.category), self._lan_info.ticket_info.get_price(new_ticket.category),
f"TICKET {new_ticket.ticket_id}" f"TICKET {new_ticket.ticket_id}"
@ -54,20 +54,20 @@ class TicketingService:
raise RuntimeError("An unknown error occurred while purchasing ticket") raise RuntimeError("An unknown error occurred while purchasing ticket")
def refund_ticket(self, user_id: int) -> bool: async def refund_ticket(self, user_id: int) -> bool:
user_ticket = self.get_user_ticket(user_id) user_ticket = await self.get_user_ticket(user_id)
if not user_ticket: if not user_ticket:
return False return False
if self._db_service.delete_ticket(user_ticket.ticket_id): if self._db_service.delete_ticket(user_ticket.ticket_id):
self._accounting_service.add_balance(user_id, self._lan_info.ticket_info.get_price(user_ticket.category), f"TICKET REFUND {user_ticket.ticket_id}") await self._accounting_service.add_balance(user_id, self._lan_info.ticket_info.get_price(user_ticket.category), f"TICKET REFUND {user_ticket.ticket_id}")
logger.debug(f"User {user_id} refunded ticket {user_ticket.ticket_id}") logger.debug(f"User {user_id} refunded ticket {user_ticket.ticket_id}")
return True return True
return False return False
def transfer_ticket(self, ticket_id: int, user_id: int) -> bool: async def transfer_ticket(self, ticket_id: int, user_id: int) -> bool:
return self._db_service.change_ticket_owner(ticket_id, user_id) return await self._db_service.change_ticket_owner(ticket_id, user_id)
def get_user_ticket(self, user_id: int) -> Optional[Ticket]: async def get_user_ticket(self, user_id: int) -> Optional[Ticket]:
return self._db_service.get_ticket_for_user(user_id) return await self._db_service.get_ticket_for_user(user_id)

View File

@ -17,26 +17,26 @@ class UserService:
def __init__(self, db_service: DatabaseService) -> None: def __init__(self, db_service: DatabaseService) -> None:
self._db_service = db_service self._db_service = db_service
def get_all_users(self) -> list[User]: async def get_all_users(self) -> list[User]:
return self._db_service.get_all_users() return await self._db_service.get_all_users()
def get_user(self, accessor: Optional[Union[str, int]]) -> Optional[User]: async def get_user(self, accessor: Optional[Union[str, int]]) -> Optional[User]:
if accessor is None: if accessor is None:
return return
if isinstance(accessor, int): if isinstance(accessor, int):
return self._db_service.get_user_by_id(accessor) return await self._db_service.get_user_by_id(accessor)
accessor = accessor.lower() accessor = accessor.lower()
if "@" in accessor: if "@" in accessor:
return self._db_service.get_user_by_mail(accessor) return await self._db_service.get_user_by_mail(accessor)
return self._db_service.get_user_by_name(accessor) return await self._db_service.get_user_by_name(accessor)
def set_profile_picture(self, user_id: int, picture: bytes) -> None: async def set_profile_picture(self, user_id: int, picture: bytes) -> None:
self._db_service.set_user_profile_picture(user_id, picture) await self._db_service.set_user_profile_picture(user_id, picture)
def get_profile_picture(self, user_id: int) -> bytes: async def get_profile_picture(self, user_id: int) -> bytes:
return self._db_service.get_user_profile_picture(user_id) return await self._db_service.get_user_profile_picture(user_id)
def create_user(self, user_name: str, user_mail: str, password_clear_text: str) -> User: async def create_user(self, user_name: str, user_mail: str, password_clear_text: str) -> User:
disallowed_char = self._check_for_disallowed_char(user_name) disallowed_char = self._check_for_disallowed_char(user_name)
if disallowed_char: if disallowed_char:
raise NameNotAllowedError(disallowed_char) raise NameNotAllowedError(disallowed_char)
@ -44,17 +44,17 @@ class UserService:
user_name = user_name.lower() user_name = user_name.lower()
hashed_pw = sha256(password_clear_text.encode(encoding="utf-8")).hexdigest() hashed_pw = sha256(password_clear_text.encode(encoding="utf-8")).hexdigest()
return self._db_service.create_user(user_name, user_mail, hashed_pw) return await self._db_service.create_user(user_name, user_mail, hashed_pw)
def update_user(self, user: User) -> User: async def update_user(self, user: User) -> User:
disallowed_char = self._check_for_disallowed_char(user.user_name) disallowed_char = self._check_for_disallowed_char(user.user_name)
if disallowed_char: if disallowed_char:
raise NameNotAllowedError(disallowed_char) raise NameNotAllowedError(disallowed_char)
user.user_name = user.user_name.lower() user.user_name = user.user_name.lower()
return self._db_service.update_user(user) return await self._db_service.update_user(user)
def is_login_valid(self, user_name_or_mail: str, password_clear_text: str) -> bool: async def is_login_valid(self, user_name_or_mail: str, password_clear_text: str) -> bool:
user = self.get_user(user_name_or_mail) user = await self.get_user(user_name_or_mail)
if not user: if not user:
return False return False
return user.user_password == sha256(password_clear_text.encode(encoding="utf-8")).hexdigest() return user.user_password == sha256(password_clear_text.encode(encoding="utf-8")).hexdigest()

View File

@ -17,3 +17,6 @@ class User:
is_admin: bool is_admin: bool
created_at: datetime created_at: datetime
last_updated_at: datetime last_updated_at: datetime
def __hash__(self) -> int:
return hash(f"{self.user_id}{self.user_name}{self.user_mail}")