Compare commits

..

2 Commits

Author SHA1 Message Date
David Rodenkirchen
005cb81ce8 add support for vertical seat pixels 2026-02-22 04:33:05 +01:00
David Rodenkirchen
8ef7e9259f build internal LAN version 2026-02-22 03:07:42 +01:00
38 changed files with 332 additions and 557 deletions

View File

@ -1 +1 @@
0.3.6 0.3.3

View File

@ -1,10 +0,0 @@
-- =====================================================
-- Adds type of participant to tournament
-- =====================================================
ALTER TABLE `tournaments` ADD COLUMN `participant_type` ENUM('PLAYER','TEAM') NOT NULL DEFAULT 'PLAYER' AFTER `created_at`;
ALTER TABLE `tournament_participants`
CHANGE COLUMN `user_id` `user_id` INT(11) NULL AFTER `tournament_id`,
ADD COLUMN `team_id` INT(11) NULL AFTER `user_id`,
ADD CONSTRAINT `fk_tp_team` FOREIGN KEY (`team_id`) REFERENCES `teams` (`id`) ON UPDATE RESTRICT ON DELETE RESTRICT;

View File

@ -1,5 +0,0 @@
-- Apply this patch after using create_database.sql to extend the schema to support fallback passwords
ALTER TABLE users
ADD COLUMN user_fallback_password VARCHAR(255) DEFAULT NULL
AFTER user_password;

View File

@ -1,17 +1,18 @@
import logging import logging
from asyncio import get_event_loop
import sys import sys
from pathlib import Path from pathlib import Path
from uuid import uuid4
from rio import App, Theme, Color, Font, ComponentPage, Session from rio import App, Theme, Color, Font, ComponentPage, Session
from from_root import from_root from from_root import from_root
from src.ezgg_lan_manager import pages, init_services, LocalDataService from src.ezgg_lan_manager import pages, init_services
from src.ezgg_lan_manager.helpers.LoggedInGuard import logged_in_guard, not_logged_in_guard, team_guard from src.ezgg_lan_manager.helpers.LoggedInGuard import logged_in_guard, not_logged_in_guard, team_guard
from src.ezgg_lan_manager.services.DatabaseService import NoDatabaseConnectionError
from src.ezgg_lan_manager.services.LocalDataService import LocalData from src.ezgg_lan_manager.services.LocalDataService import LocalData
from src.ezgg_lan_manager.types.UserSession import UserSession from src.ezgg_lan_manager.types.SessionStorage import SessionStorage
logger = logging.getLogger("EzggLanManager") logger = logging.getLogger("EzggLanManager")
@ -29,19 +30,14 @@ if __name__ == "__main__":
corner_radius_large=0, corner_radius_large=0,
font=Font(from_root("src/ezgg_lan_manager/assets/fonts/joystix.otf")) font=Font(from_root("src/ezgg_lan_manager/assets/fonts/joystix.otf"))
) )
default_attachments: list = [LocalData(stored_session_token=None)] default_attachments: list = [LocalData()]
default_attachments.extend(init_services()) default_attachments.extend(init_services())
lan_info = default_attachments[3].get_lan_info() lan_info = default_attachments[3].get_lan_info()
async def on_session_start(session: Session) -> None: async def on_session_start(session: Session) -> None:
# Use this line to fake being any user without having to log in
session.attach(UserSession(id=uuid4(), user_id=30, is_team_member=True))
await session.set_title(lan_info.name) await session.set_title(lan_info.name)
if session[LocalData].stored_session_token: session.attach(SessionStorage())
user_session = session[LocalDataService].verify_token(session[LocalData].stored_session_token)
if user_session is not None:
session.attach(user_session)
async def on_app_start(a: App) -> None: async def on_app_start(a: App) -> None:
init_result = await a.default_attachments[4].init_db_pool() init_result = await a.default_attachments[4].init_db_pool()

View File

@ -10,7 +10,6 @@ from src.ezgg_lan_manager.services.DatabaseService import DatabaseService
from src.ezgg_lan_manager.services.LocalDataService import LocalDataService from src.ezgg_lan_manager.services.LocalDataService import LocalDataService
from src.ezgg_lan_manager.services.MailingService import MailingService from src.ezgg_lan_manager.services.MailingService import MailingService
from src.ezgg_lan_manager.services.NewsService import NewsService from src.ezgg_lan_manager.services.NewsService import NewsService
from src.ezgg_lan_manager.services.RefreshService import RefreshService
from src.ezgg_lan_manager.services.ReceiptPrintingService import ReceiptPrintingService from src.ezgg_lan_manager.services.ReceiptPrintingService import ReceiptPrintingService
from src.ezgg_lan_manager.services.SeatingService import SeatingService from src.ezgg_lan_manager.services.SeatingService import SeatingService
from src.ezgg_lan_manager.services.TeamService import TeamService from src.ezgg_lan_manager.services.TeamService import TeamService
@ -20,7 +19,7 @@ from src.ezgg_lan_manager.services.UserService import UserService
from src.ezgg_lan_manager.types import * from src.ezgg_lan_manager.types import *
# Inits services in the correct order # Inits services in the correct order
def init_services() -> tuple[AccountingService, CateringService, ConfigurationService, DatabaseService, MailingService, NewsService, SeatingService, TicketingService, UserService, LocalDataService, ReceiptPrintingService, TournamentService, TeamService, RefreshService]: def init_services() -> tuple[AccountingService, CateringService, ConfigurationService, DatabaseService, MailingService, NewsService, SeatingService, TicketingService, UserService, LocalDataService, ReceiptPrintingService, TournamentService, TeamService]:
logging.basicConfig(level=logging.DEBUG) logging.basicConfig(level=logging.DEBUG)
configuration_service = ConfigurationService(from_root("config.toml")) configuration_service = ConfigurationService(from_root("config.toml"))
db_service = DatabaseService(configuration_service.get_database_configuration()) db_service = DatabaseService(configuration_service.get_database_configuration())
@ -35,6 +34,6 @@ def init_services() -> tuple[AccountingService, CateringService, ConfigurationSe
local_data_service = LocalDataService() local_data_service = LocalDataService()
tournament_service = TournamentService(db_service, user_service) tournament_service = TournamentService(db_service, user_service)
team_service = TeamService(db_service) team_service = TeamService(db_service)
refresh_service = RefreshService()
return accounting_service, catering_service, configuration_service, db_service, mailing_service, news_service, seating_service, ticketing_service, user_service, local_data_service, receipt_printing_service, tournament_service, team_service, refresh_service
return accounting_service, catering_service, configuration_service, db_service, mailing_service, news_service, seating_service, ticketing_service, user_service, local_data_service, receipt_printing_service, tournament_service, team_service

View File

@ -0,0 +1,38 @@
from asyncio import sleep
from rio import Text, Component, TextStyle
class AnimatedText(Component):
def __post_init__(self) -> None:
self._display_printing: list[bool] = [False]
self.text_comp = Text("")
async def display_text(self, success: bool, text: str, speed: float = 0.06, font_size: float = 0.9) -> None:
if self._display_printing[0]:
return
else:
self._display_printing[0] = True
self.text_comp.text = ""
if success:
self.text_comp.style = TextStyle(
fill=self.session.theme.success_color,
font_size=font_size
)
for c in text:
self.text_comp.text = self.text_comp.text + c
self.text_comp.force_refresh()
await sleep(speed)
else:
self.text_comp.style = TextStyle(
fill=self.session.theme.danger_color,
font_size=font_size
)
for c in text:
self.text_comp.text = self.text_comp.text + c
self.text_comp.force_refresh()
await sleep(speed)
self._display_printing[0] = False
def build(self) -> Component:
return self.text_comp

View File

@ -46,10 +46,10 @@ class CateringSelectionItem(Component):
Text(AccountingService.make_euro_string_from_decimal(self.article_price), Text(AccountingService.make_euro_string_from_decimal(self.article_price),
style=TextStyle(fill=self.session.theme.background_color, font_size=0.9)), style=TextStyle(fill=self.session.theme.background_color, font_size=0.9)),
IconButton( IconButton(
icon="material/add" if self.is_sensitive else "material/do_not_disturb_on_total_silence", icon="material/add",
min_size=2, min_size=2,
color=self.session.theme.success_color if self.is_sensitive else self.session.theme.danger_color, color=self.session.theme.success_color,
style="colored-text", style="plain-text",
on_press=lambda: self.on_add_callback(self.article_id), on_press=lambda: self.on_add_callback(self.article_id),
is_sensitive=self.is_sensitive is_sensitive=self.is_sensitive
), ),

View File

@ -1,29 +1,47 @@
from typing import Optional, Callable from typing import Optional, Callable
from rio import Component, event, Spacer, Card, Column, Text, TextStyle from rio import *
from src.ezgg_lan_manager.services.ConfigurationService import ConfigurationService from src.ezgg_lan_manager import ConfigurationService, UserService, LocalDataService
from src.ezgg_lan_manager.services.UserService import UserService
from src.ezgg_lan_manager.components.DesktopNavigationButton import DesktopNavigationButton from src.ezgg_lan_manager.components.DesktopNavigationButton import DesktopNavigationButton
from src.ezgg_lan_manager.components.NavigationSponsorBox import NavigationSponsorBox from src.ezgg_lan_manager.components.NavigationSponsorBox import NavigationSponsorBox
from src.ezgg_lan_manager.components.UserInfoAndLoginBox import UserInfoAndLoginBox from src.ezgg_lan_manager.components.UserInfoAndLoginBox import UserInfoAndLoginBox
from src.ezgg_lan_manager.services.LocalDataService import LocalData
from src.ezgg_lan_manager.types.SessionStorage import SessionStorage
from src.ezgg_lan_manager.types.User import User from src.ezgg_lan_manager.types.User import User
from src.ezgg_lan_manager.types.UserSession import UserSession
class DesktopNavigation(Component): class DesktopNavigation(Component):
user: Optional[User] = None user: Optional[User] = None
force_login_box_refresh: list[Callable] = []
@event.on_populate @event.on_populate
async def on_populate(self) -> None: async def async_init(self) -> None:
self.session[SessionStorage].subscribe_to_logged_in_or_out_event(str(self.__class__), self.async_init)
local_data = self.session[LocalData]
if local_data.stored_session_token:
session_ = self.session[LocalDataService].verify_token(local_data.stored_session_token)
if session_:
self.session.detach(SessionStorage)
self.session.attach(session_)
self.user = await self.session[UserService].get_user(session_.user_id)
try: try:
self.user = await self.session[UserService].get_user(self.session[UserSession].user_id) # Hack-around, maybe fix in the future
except KeyError: self.force_login_box_refresh[-1]()
except IndexError:
pass
return
if self.session[SessionStorage].user_id:
self.user = await self.session[UserService].get_user(self.session[SessionStorage].user_id)
else:
self.user = None self.user = None
def build(self) -> Component: def build(self) -> Component:
lan_info = self.session[ConfigurationService].get_lan_info() lan_info = self.session[ConfigurationService].get_lan_info()
user_info_and_login_box = UserInfoAndLoginBox(state_changed_cb=self.on_populate) user_info_and_login_box = UserInfoAndLoginBox()
self.force_login_box_refresh.append(user_info_and_login_box.force_refresh)
navigation = [ navigation = [
DesktopNavigationButton("News", "./news"), DesktopNavigationButton("News", "./news"),
Spacer(min_height=0.7), Spacer(min_height=0.7),

View File

@ -1,13 +1,10 @@
import uuid
from rio import Component, TextStyle, Color, TextInput, Button, Text, Rectangle, Column, Row, Spacer, \ from rio import Component, TextStyle, Color, TextInput, Button, Text, Rectangle, Column, Row, Spacer, \
EventHandler, Webview EventHandler
from src.ezgg_lan_manager import RefreshService
from src.ezgg_lan_manager.services.LocalDataService import LocalDataService, LocalData from src.ezgg_lan_manager.services.LocalDataService import LocalDataService, LocalData
from src.ezgg_lan_manager.services.UserService import UserService from src.ezgg_lan_manager.services.UserService import UserService
from src.ezgg_lan_manager.types.SessionStorage import SessionStorage
from src.ezgg_lan_manager.types.User import User from src.ezgg_lan_manager.types.User import User
from src.ezgg_lan_manager.types.UserSession import UserSession
class LoginBox(Component): class LoginBox(Component):
@ -29,13 +26,11 @@ class LoginBox(Component):
self.password_input_is_valid = True self.password_input_is_valid = True
self.login_button_is_loading = False self.login_button_is_loading = False
self.is_account_locked = False self.is_account_locked = False
user_session = UserSession(id=uuid.uuid4(), user_id=user.user_id, is_team_member=user.is_team_member) await self.session[SessionStorage].set_user_id_and_team_member_flag(user.user_id, user.is_team_member)
self.session.attach(user_session) token = self.session[LocalDataService].set_session(self.session[SessionStorage])
token = self.session[LocalDataService].set_session(user_session)
self.session[LocalData].stored_session_token = token self.session[LocalData].stored_session_token = token
self.session.attach(self.session[LocalData]) self.session.attach(self.session[LocalData])
await self.status_change_cb() self.status_change_cb()
await self.session[RefreshService].trigger_refresh()
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

View File

@ -1,10 +1,11 @@
from decimal import Decimal from decimal import Decimal
from functools import partial
from typing import Optional, Callable from typing import Optional, Callable
from rio import Component, Column, Text, TextStyle, Button, Spacer, event from rio import Component, Column, Text, TextStyle, Button, Spacer, event
from src.ezgg_lan_manager import TicketingService from src.ezgg_lan_manager import TicketingService
from src.ezgg_lan_manager.types.UserSession import UserSession from src.ezgg_lan_manager.types.SessionStorage import SessionStorage
class SeatingPlanInfoBox(Component): class SeatingPlanInfoBox(Component):
@ -21,14 +22,11 @@ class SeatingPlanInfoBox(Component):
@event.on_populate @event.on_populate
async def check_ticket(self) -> None: async def check_ticket(self) -> None:
try: if self.session[SessionStorage].user_id:
user_id = self.session[UserSession].user_id user_ticket = await self.session[TicketingService].get_user_ticket(self.session[SessionStorage].user_id)
user_ticket = await self.session[TicketingService].get_user_ticket(user_id)
self.has_user_ticket = not (user_ticket is None) self.has_user_ticket = not (user_ticket is None)
self.booking_button_text = "Buchen" if self.has_user_ticket else "Ticket kaufen" self.booking_button_text = "Buchen" if self.has_user_ticket else "Ticket kaufen"
self.force_refresh() self.force_refresh()
except KeyError:
return
async def purchase_clicked(self): async def purchase_clicked(self):
if self.has_user_ticket: if self.has_user_ticket:
@ -37,11 +35,6 @@ class SeatingPlanInfoBox(Component):
self.session.navigate_to("./buy_ticket") self.session.navigate_to("./buy_ticket")
def build(self) -> Component: def build(self) -> Component:
try:
user_id = self.session[UserSession].user_id
except KeyError:
user_id = None
if self.override_text: if self.override_text:
return Column(Text(self.override_text, margin=1, return Column(Text(self.override_text, margin=1,
style=TextStyle(fill=self.session.theme.neutral_color, font_size=1.4), overflow="wrap", style=TextStyle(fill=self.session.theme.neutral_color, font_size=1.4), overflow="wrap",
@ -82,7 +75,7 @@ class SeatingPlanInfoBox(Component):
grow_y=False, grow_y=False,
is_sensitive=not self.is_booking_blocked, is_sensitive=not self.is_booking_blocked,
on_press=self.purchase_clicked on_press=self.purchase_clicked
) if user_id is not None else Text(f"Du musst eingeloggt sein um einen Sitzplatz zu buchen", ) if self.session[SessionStorage].user_id else Text(f"Du musst eingeloggt sein um einen Sitzplatz zu buchen",
margin=1, margin=1,
style=TextStyle(fill=self.session.theme.neutral_color), style=TextStyle(fill=self.session.theme.neutral_color),
overflow="wrap", justify="center"), overflow="wrap", justify="center"),

View File

@ -4,7 +4,7 @@ from rio import Component, Text, Icon, TextStyle, Rectangle, Spacer, Color, Poin
from typing import Optional, Callable, Literal from typing import Optional, Callable, Literal
from src.ezgg_lan_manager.types.Seat import Seat from src.ezgg_lan_manager.types.Seat import Seat
from src.ezgg_lan_manager.types.UserSession import UserSession from src.ezgg_lan_manager.types.SessionStorage import SessionStorage
class SeatPixel(Component): class SeatPixel(Component):
@ -14,11 +14,7 @@ class SeatPixel(Component):
seat_orientation: Literal["top", "bottom", "left", "right"] seat_orientation: Literal["top", "bottom", "left", "right"]
def determine_color(self) -> Color: def determine_color(self) -> Color:
try: if self.seat.user is not None and self.seat.user.user_id == self.session[SessionStorage].user_id:
user_id = self.session[UserSession].user_id
except KeyError:
user_id = None
if self.seat.user is not None and self.seat.user.user_id == user_id:
return Color.from_hex("800080") return Color.from_hex("800080")
elif self.seat.is_blocked or self.seat.user is not None: elif self.seat.is_blocked or self.seat.user is not None:
return self.session.theme.danger_color return self.session.theme.danger_color

View File

@ -1,6 +1,5 @@
from asyncio import sleep, create_task from asyncio import sleep, create_task
from decimal import Decimal from decimal import Decimal
from typing import Optional
from rio import Component, Column, Text, TextStyle, Button, Row, ScrollContainer, Spacer, Popup, Table, event, Card from rio import Component, Column, Text, TextStyle, Button, Row, ScrollContainer, Spacer, Popup, Table, event, Card
@ -9,7 +8,7 @@ from src.ezgg_lan_manager.components.CateringOrderItem import CateringOrderItem
from src.ezgg_lan_manager.services.AccountingService import AccountingService from src.ezgg_lan_manager.services.AccountingService import AccountingService
from src.ezgg_lan_manager.services.CateringService import CateringService, CateringError, CateringErrorType from src.ezgg_lan_manager.services.CateringService import CateringService, CateringError, CateringErrorType
from src.ezgg_lan_manager.types.CateringOrder import CateringOrder, CateringMenuItemsWithAmount from src.ezgg_lan_manager.types.CateringOrder import CateringOrder, CateringMenuItemsWithAmount
from src.ezgg_lan_manager.types.UserSession import UserSession from src.ezgg_lan_manager.types.SessionStorage import SessionStorage
POPUP_CLOSE_TIMEOUT_SECONDS = 3 POPUP_CLOSE_TIMEOUT_SECONDS = 3
@ -23,21 +22,16 @@ class ShoppingCartAndOrders(Component):
@event.periodic(5) @event.periodic(5)
async def periodic_refresh_of_orders(self) -> None: async def periodic_refresh_of_orders(self) -> None:
user_id = self._get_user_id() if not self.show_cart and not self.popup_is_shown:
if not self.show_cart and not self.popup_is_shown and user_id is not None: self.orders = await self.session[CateringService].get_orders_for_user(self.session[SessionStorage].user_id)
self.orders = await self.session[CateringService].get_orders_for_user(user_id)
async def switch(self) -> None: async def switch(self) -> None:
self.show_cart = not self.show_cart self.show_cart = not self.show_cart
user_id = self._get_user_id() self.orders = await self.session[CateringService].get_orders_for_user(self.session[SessionStorage].user_id)
if user_id is not None:
self.orders = await self.session[CateringService].get_orders_for_user(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]
user_id = self._get_user_id() user_id = self.session[SessionStorage].user_id
if user_id is None:
return
cart = catering_service.get_cart(user_id) cart = catering_service.get_cart(user_id)
try: try:
cart.pop(list_id) cart.pop(list_id)
@ -47,16 +41,13 @@ class ShoppingCartAndOrders(Component):
self.force_refresh() self.force_refresh()
async def on_empty_cart_pressed(self) -> None: async def on_empty_cart_pressed(self) -> None:
user_id = self._get_user_id() self.session[CateringService].save_cart(self.session[SessionStorage].user_id, [])
if user_id is None:
return
self.session[CateringService].save_cart(user_id, [])
self.force_refresh() self.force_refresh()
async def on_add_item(self, article_id: int) -> None: async def on_add_item(self, article_id: int) -> None:
catering_service = self.session[CateringService] catering_service = self.session[CateringService]
user_id = self._get_user_id() user_id = self.session[SessionStorage].user_id
if user_id is None: if not user_id:
return return
cart = catering_service.get_cart(user_id) cart = catering_service.get_cart(user_id)
item_to_add = await catering_service.get_menu_item_by_id(article_id) item_to_add = await catering_service.get_menu_item_by_id(article_id)
@ -77,9 +68,7 @@ class ShoppingCartAndOrders(Component):
self.order_button_loading = True self.order_button_loading = True
self.force_refresh() self.force_refresh()
user_id = self._get_user_id() user_id = self.session[SessionStorage].user_id
if user_id is None:
return
cart = self.session[CateringService].get_cart(user_id) cart = self.session[CateringService].get_cart(user_id)
show_popup_task = None show_popup_task = None
if len(cart) < 1: if len(cart) < 1:
@ -101,7 +90,7 @@ class ShoppingCartAndOrders(Component):
else: else:
show_popup_task = create_task(self.show_popup("Unbekannter Fehler", True)) show_popup_task = create_task(self.show_popup("Unbekannter Fehler", True))
else: else:
self.session[CateringService].save_cart(user_id, []) self.session[CateringService].save_cart(self.session[SessionStorage].user_id, [])
self.order_button_loading = False self.order_button_loading = False
if not show_popup_task: if not show_popup_task:
show_popup_task = create_task(self.show_popup("Bestellung erfolgreich aufgegeben!", False)) show_popup_task = create_task(self.show_popup("Bestellung erfolgreich aufgegeben!", False))
@ -144,16 +133,10 @@ class ShoppingCartAndOrders(Component):
) )
await dialog.wait_for_close() await dialog.wait_for_close()
def _get_user_id(self) -> Optional[int]:
try:
return self.session[UserSession].user_id
except KeyError:
return None
def build(self) -> Component: def build(self) -> Component:
user_id = self._get_user_id() user_id = self.session[SessionStorage].user_id
catering_service = self.session[CateringService] catering_service = self.session[CateringService]
cart = catering_service.get_cart(user_id) if user_id is not None else [] cart = catering_service.get_cart(user_id)
if self.show_cart: if self.show_cart:
cart_container = ScrollContainer( cart_container = ScrollContainer(
content=Column( content=Column(

View File

@ -2,6 +2,7 @@ from functools import partial
from typing import Callable, Optional from typing import Callable, Optional
from decimal import Decimal from decimal import Decimal
import rio
from rio import Component, Card, Column, Text, Row, Button, TextStyle, ProgressBar, event, Spacer from rio import Component, Card, Column, Text, Row, Button, TextStyle, ProgressBar, event, Spacer
from src.ezgg_lan_manager import TicketingService from src.ezgg_lan_manager import TicketingService
@ -21,10 +22,10 @@ class TicketBuyCard(Component):
available_tickets: int = 0 available_tickets: int = 0
@event.on_populate @event.on_populate
async def on_populate(self) -> None: async def async_init(self) -> None:
self.available_tickets = await self.session[TicketingService].get_available_tickets_for_category(self.category) self.available_tickets = await self.session[TicketingService].get_available_tickets_for_category(self.category)
def build(self) -> Component: def build(self) -> rio.Component:
ticket_description_style = TextStyle( ticket_description_style = TextStyle(
fill=self.session.theme.neutral_color, fill=self.session.theme.neutral_color,
font_size=1.2, font_size=1.2,

View File

@ -9,8 +9,8 @@ from rio import Component, Column, Button, Color, TextStyle, Text, TextInput, Ro
from src.ezgg_lan_manager.services.UserService import UserService, NameNotAllowedError from src.ezgg_lan_manager.services.UserService import UserService, NameNotAllowedError
from src.ezgg_lan_manager.services.ConfigurationService import ConfigurationService from src.ezgg_lan_manager.services.ConfigurationService import ConfigurationService
from src.ezgg_lan_manager.types.SessionStorage import SessionStorage
from src.ezgg_lan_manager.types.User import User from src.ezgg_lan_manager.types.User import User
from src.ezgg_lan_manager.types.UserSession import UserSession
class UserEditForm(Component): class UserEditForm(Component):
@ -35,12 +35,7 @@ class UserEditForm(Component):
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")
if self.is_own_profile: if self.is_own_profile:
try: self.user = await self.session[UserService].get_user(self.session[SessionStorage].user_id)
user_id = self.session[UserSession].user_id
except KeyError:
self.session.navigate_to("/")
else:
self.user = await self.session[UserService].get_user(user_id)
self.profile_picture = await self.session[UserService].get_profile_picture(self.user.user_id) self.profile_picture = await self.session[UserService].get_profile_picture(self.user.user_id)
else: else:
self.profile_picture = await self.session[UserService].get_profile_picture(self.user.user_id) self.profile_picture = await self.session[UserService].get_profile_picture(self.user.user_id)

View File

@ -1,18 +1,15 @@
import logging import logging
from typing import Callable
from rio import Component from rio import Component
from src.ezgg_lan_manager.components.LoginBox import LoginBox from src.ezgg_lan_manager.components.LoginBox import LoginBox
from src.ezgg_lan_manager.components.UserInfoBox import UserInfoBox from src.ezgg_lan_manager.components.UserInfoBox import UserInfoBox
from src.ezgg_lan_manager.types.UserSession import UserSession from src.ezgg_lan_manager.types.SessionStorage import SessionStorage
logger = logging.getLogger(__name__.split(".")[-1]) logger = logging.getLogger(__name__.split(".")[-1])
class UserInfoAndLoginBox(Component): class UserInfoAndLoginBox(Component):
state_changed_cb: Callable
def build(self) -> Component: def build(self) -> Component:
try: if self.session[SessionStorage].user_id is None:
user_id = self.session[UserSession].user_id return LoginBox(status_change_cb=self.force_refresh)
return UserInfoBox(status_change_cb=self.state_changed_cb, user_id=user_id) else:
except KeyError: return UserInfoBox(status_change_cb=self.force_refresh)
return LoginBox(status_change_cb=self.state_changed_cb)

View File

@ -6,7 +6,6 @@ from rio import Component, TextStyle, Color, Button, Text, Rectangle, Column, Ro
from src.ezgg_lan_manager.components.UserInfoBoxButton import UserInfoBoxButton from src.ezgg_lan_manager.components.UserInfoBoxButton import UserInfoBoxButton
from src.ezgg_lan_manager.services.LocalDataService import LocalData, LocalDataService from src.ezgg_lan_manager.services.LocalDataService import LocalData, LocalDataService
from src.ezgg_lan_manager.services.RefreshService import RefreshService
from src.ezgg_lan_manager.services.UserService import UserService from src.ezgg_lan_manager.services.UserService import UserService
from src.ezgg_lan_manager.services.AccountingService import AccountingService from src.ezgg_lan_manager.services.AccountingService import AccountingService
from src.ezgg_lan_manager.services.TicketingService import TicketingService from src.ezgg_lan_manager.services.TicketingService import TicketingService
@ -14,7 +13,7 @@ from src.ezgg_lan_manager.services.SeatingService import SeatingService
from src.ezgg_lan_manager.types.Seat import Seat from src.ezgg_lan_manager.types.Seat import Seat
from src.ezgg_lan_manager.types.Ticket import Ticket from src.ezgg_lan_manager.types.Ticket import Ticket
from src.ezgg_lan_manager.types.User import User from src.ezgg_lan_manager.types.User import User
from src.ezgg_lan_manager.types.UserSession import UserSession from src.ezgg_lan_manager.types.SessionStorage import SessionStorage
class StatusButton(Component): class StatusButton(Component):
@ -42,7 +41,6 @@ class StatusButton(Component):
class UserInfoBox(Component): class UserInfoBox(Component):
user_id: int
status_change_cb: EventHandler = None status_change_cb: EventHandler = None
TEXT_STYLE = TextStyle(fill=Color.from_hex("02dac5"), font_size=0.9) TEXT_STYLE = TextStyle(fill=Color.from_hex("02dac5"), font_size=0.9)
user: Optional[User] = None user: Optional[User] = None
@ -55,26 +53,31 @@ class UserInfoBox(Component):
return choice(["Guten Tacho", "Tuten Gag", "Servus", "Moinjour", "Hallöchen", "Heyho", "Moinsen"]) return choice(["Guten Tacho", "Tuten Gag", "Servus", "Moinjour", "Hallöchen", "Heyho", "Moinsen"])
async def logout(self) -> None: async def logout(self) -> None:
self.session.detach(UserSession) await self.session[SessionStorage].clear()
self.user = None self.user = None
self.session[LocalDataService].del_session(self.session[LocalData].stored_session_token) self.session[LocalDataService].del_session(self.session[LocalData].stored_session_token)
self.session[LocalData].stored_session_token = None self.session[LocalData].stored_session_token = None
self.session.attach(self.session[LocalData]) self.session.attach(self.session[LocalData])
await self.status_change_cb() self.status_change_cb()
await self.session[RefreshService].trigger_refresh() self.session.navigate_to("/")
@event.on_populate @event.on_populate
async def async_init(self) -> None: async def async_init(self) -> None:
self.user = await self.session[UserService].get_user(self.user_id) 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_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_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) self.user_seat = await self.session[SeatingService].get_user_seat(self.user.user_id)
self.session[AccountingService].add_update_hook(self.update) self.session[AccountingService].add_update_hook(self.update)
async def update(self) -> None: async def update(self) -> None:
self.user_balance = await self.session[AccountingService].get_balance(self.user_id) if not self.user:
self.user_ticket = await self.session[TicketingService].get_user_ticket(self.user_id) self.user = await self.session[UserService].get_user(self.session[SessionStorage].user_id)
self.user_seat = await self.session[SeatingService].get_user_seat(self.user_id) if not self.user:
return
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)
def build(self) -> Component: def build(self) -> Component:
if not self.user: if not self.user:

View File

@ -3,32 +3,22 @@ from typing import Optional
from rio import URL, GuardEvent from rio import URL, GuardEvent
from src.ezgg_lan_manager.services.UserService import UserService from src.ezgg_lan_manager.services.UserService import UserService
from src.ezgg_lan_manager.types.UserSession import UserSession from src.ezgg_lan_manager.types.SessionStorage import SessionStorage
# Guards pages against access from users that are NOT logged in # Guards pages against access from users that are NOT logged in
def logged_in_guard(event: GuardEvent) -> Optional[URL]: def logged_in_guard(event: GuardEvent) -> Optional[URL]:
try: if event.session[SessionStorage].user_id is None:
_ = event.session[UserSession].user_id
return None
except KeyError:
return URL("./") return URL("./")
# Guards pages against access from users that ARE logged in # Guards pages against access from users that ARE logged in
def not_logged_in_guard(event: GuardEvent) -> Optional[URL]: def not_logged_in_guard(event: GuardEvent) -> Optional[URL]:
try: if event.session[SessionStorage].user_id is not None:
_ = event.session[UserSession].user_id
return URL("./") return URL("./")
except KeyError:
return None
# Guards pages against access from users that are NOT logged in and NOT team members # Guards pages against access from users that are NOT logged in and NOT team members
def team_guard(event: GuardEvent) -> Optional[URL]: def team_guard(event: GuardEvent) -> Optional[URL]:
try: user_id = event.session[SessionStorage].user_id
user_id = event.session[UserSession].user_id is_team_member = event.session[SessionStorage].is_team_member
is_team_member = event.session[UserSession].is_team_member if user_id is None or not is_team_member:
if user_id and is_team_member:
return None
return URL("./")
except KeyError:
return URL("./") return URL("./")

View File

@ -1,13 +1,14 @@
from decimal import Decimal from decimal import Decimal
from functools import partial
from typing import Optional from typing import Optional
from rio import Column, Component, event, Text, TextStyle, Button, Color, Revealer, Row, ProgressCircle, Link from rio import Column, Component, event, Text, TextStyle, Button, Color, Revealer, Row, ProgressCircle, Link
from src.ezgg_lan_manager import ConfigurationService, UserService, AccountingService from src.ezgg_lan_manager import ConfigurationService, UserService, AccountingService
from src.ezgg_lan_manager.components.MainViewContentBox import MainViewContentBox from src.ezgg_lan_manager.components.MainViewContentBox import MainViewContentBox
from src.ezgg_lan_manager.types.SessionStorage import SessionStorage
from src.ezgg_lan_manager.types.Transaction import Transaction from src.ezgg_lan_manager.types.Transaction import Transaction
from src.ezgg_lan_manager.types.User import User from src.ezgg_lan_manager.types.User import User
from src.ezgg_lan_manager.types.UserSession import UserSession
class AccountPage(Component): class AccountPage(Component):
@ -20,14 +21,9 @@ class AccountPage(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} - Guthabenkonto") await self.session.set_title(f"{self.session[ConfigurationService].get_lan_info().name} - Guthabenkonto")
try: self.user = await self.session[UserService].get_user(self.session[SessionStorage].user_id)
user_id = self.session[UserSession].user_id self.balance = await self.session[AccountingService].get_balance(self.user.user_id)
except KeyError: self.transaction_history = await self.session[AccountingService].get_transaction_history(self.user.user_id)
pass
else:
self.user = await self.session[UserService].get_user(user_id)
self.balance = await self.session[AccountingService].get_balance(user_id)
self.transaction_history = await self.session[AccountingService].get_transaction_history(user_id)
async def _on_banking_info_press(self) -> None: async def _on_banking_info_press(self) -> None:
self.banking_info_revealer_open = not self.banking_info_revealer_open self.banking_info_revealer_open = not self.banking_info_revealer_open

View File

@ -2,14 +2,14 @@ from typing import Optional
from rio import Text, Column, TextStyle, Component, event, Button, Popup from rio import Text, Column, TextStyle, Component, event, Button, Popup
from src.ezgg_lan_manager import ConfigurationService, UserService, TicketingService, RefreshService from src.ezgg_lan_manager import ConfigurationService, UserService, TicketingService
from src.ezgg_lan_manager.components.MainViewContentBox import MainViewContentBox from src.ezgg_lan_manager.components.MainViewContentBox import MainViewContentBox
from src.ezgg_lan_manager.components.TicketBuyCard import TicketBuyCard from src.ezgg_lan_manager.components.TicketBuyCard import TicketBuyCard
from src.ezgg_lan_manager.services.AccountingService import InsufficientFundsError from src.ezgg_lan_manager.services.AccountingService import InsufficientFundsError
from src.ezgg_lan_manager.services.TicketingService import TicketNotAvailableError, UserAlreadyHasTicketError from src.ezgg_lan_manager.services.TicketingService import TicketNotAvailableError, UserAlreadyHasTicketError
from src.ezgg_lan_manager.types.SessionStorage import SessionStorage
from src.ezgg_lan_manager.types.Ticket import Ticket from src.ezgg_lan_manager.types.Ticket import Ticket
from src.ezgg_lan_manager.types.User import User from src.ezgg_lan_manager.types.User import User
from src.ezgg_lan_manager.types.UserSession import UserSession
class BuyTicketPage(Component): class BuyTicketPage(Component):
@ -23,18 +23,12 @@ class BuyTicketPage(Component):
@event.on_populate @event.on_populate
async def on_populate(self) -> None: async def on_populate(self) -> None:
self.session[RefreshService].subscribe(self.on_populate) self.session[SessionStorage].subscribe_to_logged_in_or_out_event(str(self.__class__), self.on_populate)
await self.session.set_title(f"{self.session[ConfigurationService].get_lan_info().name} - Ticket kaufen") await self.session.set_title(f"{self.session[ConfigurationService].get_lan_info().name} - Ticket kaufen")
try: self.user = await self.session[UserService].get_user(self.session[SessionStorage].user_id)
user_id = self.session[UserSession].user_id
except KeyError:
self.user = None
else:
self.user = await self.session[UserService].get_user(user_id)
if self.user is None: # No user logged in if self.user is None: # No user logged in
self.is_buying_enabled = False self.is_buying_enabled = False
self.is_user_logged_in = False self.is_user_logged_in = False
self.user_ticket = None
else: # User is logged in else: # User is logged in
self.is_user_logged_in = True self.is_user_logged_in = True
possible_ticket = await self.session[TicketingService].get_user_ticket(self.user.user_id) possible_ticket = await self.session[TicketingService].get_user_ticket(self.user.user_id)

View File

@ -6,9 +6,8 @@ from src.ezgg_lan_manager import ConfigurationService, CateringService
from src.ezgg_lan_manager.components.CateringSelectionItem import CateringSelectionItem from src.ezgg_lan_manager.components.CateringSelectionItem import CateringSelectionItem
from src.ezgg_lan_manager.components.MainViewContentBox import MainViewContentBox from src.ezgg_lan_manager.components.MainViewContentBox import MainViewContentBox
from src.ezgg_lan_manager.components.ShoppingCartAndOrders import ShoppingCartAndOrders from src.ezgg_lan_manager.components.ShoppingCartAndOrders import ShoppingCartAndOrders
from src.ezgg_lan_manager.services.RefreshService import RefreshService
from src.ezgg_lan_manager.types.CateringMenuItem import CateringMenuItemCategory, CateringMenuItem from src.ezgg_lan_manager.types.CateringMenuItem import CateringMenuItemCategory, CateringMenuItem
from src.ezgg_lan_manager.types.UserSession import UserSession from src.ezgg_lan_manager.types.SessionStorage import SessionStorage
class CateringPage(Component): class CateringPage(Component):
@ -16,9 +15,11 @@ class CateringPage(Component):
all_menu_items: Optional[list[CateringMenuItem]] = None all_menu_items: Optional[list[CateringMenuItem]] = None
shopping_cart_and_orders: list[ShoppingCartAndOrders] = [] shopping_cart_and_orders: list[ShoppingCartAndOrders] = []
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)
@event.on_populate @event.on_populate
async def on_populate(self) -> None: async def on_populate(self) -> None:
self.session[RefreshService].subscribe(self.on_populate)
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() self.all_menu_items = await self.session[CateringService].get_menu()
@ -33,10 +34,7 @@ class CateringPage(Component):
return list(filter(lambda item: item.category == category, all_menu_items)) return list(filter(lambda item: item.category == category, all_menu_items))
def build(self) -> Component: def build(self) -> Component:
try: user_id = self.session[SessionStorage].user_id
user_id = self.session[UserSession].user_id
except KeyError:
user_id = None
if len(self.shopping_cart_and_orders) == 0: if len(self.shopping_cart_and_orders) == 0:
self.shopping_cart_and_orders.append(ShoppingCartAndOrders()) self.shopping_cart_and_orders.append(ShoppingCartAndOrders())
if len(self.shopping_cart_and_orders) > 1: if len(self.shopping_cart_and_orders) > 1:

View File

@ -5,8 +5,8 @@ from rio import Text, Column, TextStyle, Component, event, TextInput, MultiLineT
from src.ezgg_lan_manager import ConfigurationService, UserService, MailingService from src.ezgg_lan_manager import ConfigurationService, UserService, MailingService
from src.ezgg_lan_manager.components.MainViewContentBox import MainViewContentBox from src.ezgg_lan_manager.components.MainViewContentBox import MainViewContentBox
from src.ezgg_lan_manager.types.SessionStorage import SessionStorage
from src.ezgg_lan_manager.types.User import User from src.ezgg_lan_manager.types.User import User
from src.ezgg_lan_manager.types.UserSession import UserSession
class ContactPage(Component): class ContactPage(Component):
@ -25,9 +25,9 @@ class ContactPage(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} - Kontakt") await self.session.set_title(f"{self.session[ConfigurationService].get_lan_info().name} - Kontakt")
try: if self.session[SessionStorage].user_id is not None:
self.user = await self.session[UserService].get_user(self.session[UserSession].user_id) self.user = await self.session[UserService].get_user(self.session[SessionStorage].user_id)
except KeyError: else:
self.user = None self.user = None
self.e_mail = "" if not self.user else self.user.user_mail self.e_mail = "" if not self.user else self.user.user_mail

View File

@ -1,14 +1,23 @@
from typing import Optional
from rio import Column, Component, event, Spacer from rio import Column, Component, event, Spacer
from src.ezgg_lan_manager import ConfigurationService from src.ezgg_lan_manager import ConfigurationService, UserService
from src.ezgg_lan_manager.components.MainViewContentBox import MainViewContentBox from src.ezgg_lan_manager.components.MainViewContentBox import MainViewContentBox
from src.ezgg_lan_manager.components.UserEditForm import UserEditForm from src.ezgg_lan_manager.components.UserEditForm import UserEditForm
from src.ezgg_lan_manager.types.SessionStorage import SessionStorage
from src.ezgg_lan_manager.types.User import User
class EditProfilePage(Component): class EditProfilePage(Component):
user: Optional[User] = None
pfp: Optional[bytes] = 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} - 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 build(self) -> Component: def build(self) -> Component:
return Column( return Column(

View File

@ -27,7 +27,7 @@ class ForgotPasswordPage(Component):
user = await 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_fallback_password = sha256(new_password.encode(encoding="utf-8")).hexdigest() user.user_password = sha256(new_password.encode(encoding="utf-8")).hexdigest()
await 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}",

View File

@ -11,9 +11,9 @@ from src.ezgg_lan_manager.components.MainViewContentBox import MainViewContentBo
from src.ezgg_lan_manager.components.NewTransactionForm import NewTransactionForm from src.ezgg_lan_manager.components.NewTransactionForm import NewTransactionForm
from src.ezgg_lan_manager.components.UserEditForm import UserEditForm from src.ezgg_lan_manager.components.UserEditForm import UserEditForm
from src.ezgg_lan_manager.services.AccountingService import InsufficientFundsError from src.ezgg_lan_manager.services.AccountingService import InsufficientFundsError
from src.ezgg_lan_manager.types.SessionStorage import SessionStorage
from src.ezgg_lan_manager.types.Transaction import Transaction from src.ezgg_lan_manager.types.Transaction import Transaction
from src.ezgg_lan_manager.types.User import User from src.ezgg_lan_manager.types.User import User
from src.ezgg_lan_manager.types.UserSession import UserSession
logger = logging.getLogger(__name__.split(".")[-1]) logger = logging.getLogger(__name__.split(".")[-1])
@ -84,11 +84,7 @@ class ManageUsersPage(Component):
await self.session[UserService].update_user(self.selected_user) await self.session[UserService].update_user(self.selected_user)
async def on_new_transaction(self, transaction: Transaction) -> None: async def on_new_transaction(self, transaction: Transaction) -> None:
try: if not self.session[SessionStorage].is_team_member: # Better safe than sorry
user = await self.session[UserService].get_user(self.session[UserSession].user_id)
if not user.is_team_member: # Better safe than sorry
return
except KeyError:
return return
logger.info(f"Got new transaction for user with ID '{transaction.user_id}' over " logger.info(f"Got new transaction for user with ID '{transaction.user_id}' over "

View File

@ -1,10 +1,10 @@
import logging import logging
from asyncio import sleep, create_task
from email_validator import validate_email, EmailNotValidError from email_validator import validate_email, EmailNotValidError
from rio import Column, Component, event, Text, TextStyle, TextInput, TextInputChangeEvent, Button from rio import Column, Component, event, Text, TextStyle, TextInput, TextInputChangeEvent, Button
from src.ezgg_lan_manager import ConfigurationService, UserService, MailingService from src.ezgg_lan_manager import ConfigurationService, UserService, MailingService
from src.ezgg_lan_manager.components.AnimatedText import AnimatedText
from src.ezgg_lan_manager.components.MainViewContentBox import MainViewContentBox from src.ezgg_lan_manager.components.MainViewContentBox import MainViewContentBox
MINIMUM_PASSWORD_LENGTH = 6 MINIMUM_PASSWORD_LENGTH = 6
@ -13,154 +13,125 @@ logger = logging.getLogger(__name__.split(".")[-1])
class RegisterPage(Component): class RegisterPage(Component):
pw_1: str = ""
pw_2: str = ""
email: str = ""
user_name: str = ""
pw_1_valid: bool = True
pw_2_valid: bool = True
email_valid: bool = True
submit_button_loading: bool = False
display_text: str = ""
display_text_style: TextStyle = TextStyle()
def on_pw_focus_loss(self, _: TextInputChangeEvent) -> None: def on_pw_focus_loss(self, _: TextInputChangeEvent) -> None:
if not (self.pw_1 == self.pw_2) or len(self.pw_1) < MINIMUM_PASSWORD_LENGTH: if not (self.pw_1.text == self.pw_2.text) or len(self.pw_1.text) < MINIMUM_PASSWORD_LENGTH:
self.pw_1_valid = False self.pw_1.is_valid = False
self.pw_2_valid = False self.pw_2.is_valid = False
return return
self.pw_1_valid = True self.pw_1.is_valid = True
self.pw_2_valid = True self.pw_2.is_valid = True
def on_email_focus_loss(self, change_event: TextInputChangeEvent) -> None: def on_email_focus_loss(self, change_event: TextInputChangeEvent) -> None:
try: try:
validate_email(change_event.text, check_deliverability=False) validate_email(change_event.text, check_deliverability=False)
self.email_valid = True self.email_input.is_valid = True
except EmailNotValidError: except EmailNotValidError:
self.email_valid = False self.email_input.is_valid = False
def on_user_name_focus_loss(self, _: TextInputChangeEvent) -> None: def on_user_name_focus_loss(self, _: TextInputChangeEvent) -> None:
current_text = self.user_name current_text = self.user_name_input.text
if len(current_text) > UserService.MAX_USERNAME_LENGTH: if len(current_text) > UserService.MAX_USERNAME_LENGTH:
self.user_name = current_text[:UserService.MAX_USERNAME_LENGTH] self.user_name_input.text = current_text[:UserService.MAX_USERNAME_LENGTH]
async def on_submit_button_pressed(self) -> None: async def on_submit_button_pressed(self) -> None:
self.submit_button_loading = True self.submit_button.is_loading = True
self.submit_button.force_refresh()
if len(self.user_name) < 1: if len(self.user_name_input.text) < 1:
await self.display_animated_text(False, "Nutzername darf nicht leer sein!") await self.animated_text.display_text(False, "Nutzername darf nicht leer sein!")
self.submit_button_loading = False self.submit_button.is_loading = False
return return
if not (self.pw_1 == self.pw_2): if not (self.pw_1.text == self.pw_2.text):
await self.display_animated_text(False, "Passwörter stimmen nicht überein!") await self.animated_text.display_text(False, "Passwörter stimmen nicht überein!")
self.submit_button_loading = False self.submit_button.is_loading = False
return return
if len(self.pw_1) < MINIMUM_PASSWORD_LENGTH: if len(self.pw_1.text) < MINIMUM_PASSWORD_LENGTH:
await self.display_animated_text(False, f"Passwort muss mindestens {MINIMUM_PASSWORD_LENGTH} Zeichen lang sein!") await self.animated_text.display_text(False, f"Passwort muss mindestens {MINIMUM_PASSWORD_LENGTH} Zeichen lang sein!")
self.submit_button_loading = False self.submit_button.is_loading = False
return return
if not self.email_valid or len(self.email) < 3: if not self.email_input.is_valid or len(self.email_input.text) < 3:
await self.display_animated_text(False, "E-Mail Adresse ungültig!") await self.animated_text.display_text(False, "E-Mail Adresse ungültig!")
self.submit_button_loading = False self.submit_button.is_loading = False
return return
user_service = self.session[UserService] user_service = self.session[UserService]
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 await user_service.get_user(self.email) is not None or await user_service.get_user(self.user_name) 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.display_animated_text(False, "Benutzername oder E-Mail bereits registriert!") await self.animated_text.display_text(False, "Benutzername oder E-Mail bereits regestriert!")
self.submit_button_loading = False self.submit_button.is_loading = False
return return
try: try:
new_user = await user_service.create_user(self.user_name, self.email, self.pw_1) 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:
logger.error(f"create_user returned: {new_user}") logger.warning(f"UserService.create_user returned: {new_user}") # ToDo: Seems like the user is created fine, even if not returned #FixMe
raise Exception(f"create_user returned: {new_user}")
except Exception as e: except Exception as e:
logger.error(f"Unknown error during new user registration: {e}") logger.error(f"Unknown error during new user registration: {e}")
await self.display_animated_text(False, "Es ist ein unbekannter Fehler aufgetreten :(") await self.animated_text.display_text(False, "Es ist ein unbekannter Fehler aufgetreten :(")
self.submit_button_loading = False self.submit_button.is_loading = False
return return
await mailing_service.send_email( await mailing_service.send_email(
subject="Erfolgreiche Registrierung", subject="Erfolgreiche Registrierung",
body=f"Hallo {self.user_name},\n\n" body=f"Hallo {self.user_name_input.text},\n\n"
f"Du hast dich erfolgreich beim EZGG-LAN Manager für {lan_info.name} {lan_info.iteration} registriert.\n\n" f"Du hast dich erfolgreich beim EZGG-LAN Manager für {lan_info.name} {lan_info.iteration} registriert.\n\n"
f"Wenn du dich nicht registriert hast, kontaktiere bitte unser Team über unsere Homepage.\n\n" f"Wenn du dich nicht registriert hast, kontaktiere bitte unser Team über unsere Homepage.\n\n"
f"Liebe Grüße\nDein {lan_info.name} - Team", f"Liebe Grüße\nDein {lan_info.name} - Team",
receiver=self.email receiver=self.email_input.text
) )
self.submit_button_loading = False self.submit_button.is_loading = False
await self.display_animated_text(True, "Erfolgreich registriert!") await self.animated_text.display_text(True, "Erfolgreich registriert!")
@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} - Registrieren") await self.session.set_title(f"{self.session[ConfigurationService].get_lan_info().name} - Registrieren")
async def display_animated_text(self, success: bool, text: str) -> None:
self.display_text = ""
style = TextStyle(
fill=self.session.theme.success_color if success else self.session.theme.danger_color,
font_size=0.9
)
self.display_text_style = style
_ = create_task(self._animate_text(text))
async def _animate_text(self, text: str) -> None:
for c in text:
self.display_text += c
await sleep(0.06)
def build(self) -> Component: def build(self) -> Component:
user_name_input = TextInput( self.user_name_input = TextInput(
label="Benutzername", label="Benutzername",
text=self.bind().user_name, text="",
margin_left=1, margin_left=1,
margin_right=1, margin_right=1,
margin_bottom=1, margin_bottom=1,
grow_x=True, grow_x=True,
on_lose_focus=self.on_user_name_focus_loss on_lose_focus=self.on_user_name_focus_loss
) )
email_input = TextInput( self.email_input = TextInput(
label="E-Mail Adresse", label="E-Mail Adresse",
text=self.bind().email, text="",
margin_left=1, margin_left=1,
margin_right=1, margin_right=1,
margin_bottom=1, margin_bottom=1,
grow_x=True, grow_x=True,
on_lose_focus=self.on_email_focus_loss, on_lose_focus=self.on_email_focus_loss
is_valid=self.email_valid
) )
pw_1_input = TextInput( self.pw_1 = TextInput(
label="Passwort", label="Passwort",
text=self.bind().pw_1, text="",
margin_left=1, margin_left=1,
margin_right=1, margin_right=1,
margin_bottom=1, margin_bottom=1,
grow_x=True, grow_x=True,
is_secret=True, is_secret=True,
on_lose_focus=self.on_pw_focus_loss, on_lose_focus=self.on_pw_focus_loss
is_valid=self.pw_1_valid
) )
pw_2_input = TextInput( self.pw_2 = TextInput(
label="Passwort wiederholen", label="Passwort wiederholen",
text=self.bind().pw_2, text="",
margin_left=1, margin_left=1,
margin_right=1, margin_right=1,
margin_bottom=1, margin_bottom=1,
grow_x=True, grow_x=True,
is_secret=True, is_secret=True,
on_lose_focus=self.on_pw_focus_loss, on_lose_focus=self.on_pw_focus_loss
is_valid=self.pw_2_valid
) )
submit_button = Button( self.submit_button = Button(
content=Text( content=Text(
"Registrieren", "Registrieren",
style=TextStyle(fill=self.session.theme.background_color, font_size=0.9), style=TextStyle(fill=self.session.theme.background_color, font_size=0.9),
@ -174,8 +145,13 @@ class RegisterPage(Component):
shape="rectangle", shape="rectangle",
style="minor", style="minor",
color=self.session.theme.secondary_color, color=self.session.theme.secondary_color,
on_press=self.on_submit_button_pressed, on_press=self.on_submit_button_pressed
is_loading=self.submit_button_loading )
self.animated_text = AnimatedText(
margin_top=2,
margin_left=1,
margin_right=1,
margin_bottom=2
) )
return Column( return Column(
MainViewContentBox( MainViewContentBox(
@ -190,12 +166,12 @@ class RegisterPage(Component):
margin_bottom=2, margin_bottom=2,
align_x=0.5 align_x=0.5
), ),
user_name_input, self.user_name_input,
email_input, self.email_input,
pw_1_input, self.pw_1,
pw_2_input, self.pw_2,
submit_button, self.submit_button,
Text(self.display_text, margin_top=2, margin_left=1, margin_right=1, margin_bottom=2, style=self.display_text_style) self.animated_text
) )
), ),
align_y=0, align_y=0,

View File

@ -12,8 +12,8 @@ from src.ezgg_lan_manager.components.SeatingPlanInfoBox import SeatingPlanInfoBo
from src.ezgg_lan_manager.components.SeatingPurchaseBox import SeatingPurchaseBox from src.ezgg_lan_manager.components.SeatingPurchaseBox import SeatingPurchaseBox
from src.ezgg_lan_manager.services.SeatingService import NoTicketError, SeatNotFoundError, WrongCategoryError, SeatAlreadyTakenError from src.ezgg_lan_manager.services.SeatingService import NoTicketError, SeatNotFoundError, WrongCategoryError, SeatAlreadyTakenError
from src.ezgg_lan_manager.types.Seat import Seat from src.ezgg_lan_manager.types.Seat import Seat
from src.ezgg_lan_manager.types.SessionStorage import SessionStorage
from src.ezgg_lan_manager.types.User import User from src.ezgg_lan_manager.types.User import User
from src.ezgg_lan_manager.types.UserSession import UserSession
logger = logging.getLogger(__name__.split(".")[-1]) logger = logging.getLogger(__name__.split(".")[-1])
@ -37,10 +37,7 @@ class SeatingPlanPage(Component):
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} - Sitzplan") await self.session.set_title(f"{self.session[ConfigurationService].get_lan_info().name} - Sitzplan")
self.seating_info = await self.session[SeatingService].get_seating() self.seating_info = await self.session[SeatingService].get_seating()
try: self.user = await self.session[UserService].get_user(self.session[SessionStorage].user_id)
self.user = await self.session[UserService].get_user(self.session[UserSession].user_id)
except KeyError:
self.user = None
if not self.user: if not self.user:
self.is_booking_blocked = True self.is_booking_blocked = True
else: else:

View File

@ -6,12 +6,11 @@ from src.ezgg_lan_manager import ConfigurationService
from src.ezgg_lan_manager.components.MainViewContentBox import MainViewContentBox from src.ezgg_lan_manager.components.MainViewContentBox import MainViewContentBox
from src.ezgg_lan_manager.components.TeamRevealer import TeamRevealer from src.ezgg_lan_manager.components.TeamRevealer import TeamRevealer
from src.ezgg_lan_manager.components.TeamsDialogHandler import * from src.ezgg_lan_manager.components.TeamsDialogHandler import *
from src.ezgg_lan_manager.services.RefreshService import RefreshService
from src.ezgg_lan_manager.services.TeamService import TeamService from src.ezgg_lan_manager.services.TeamService import TeamService
from src.ezgg_lan_manager.services.UserService import UserService from src.ezgg_lan_manager.services.UserService import UserService
from src.ezgg_lan_manager.types.SessionStorage import SessionStorage
from src.ezgg_lan_manager.types.Team import Team from src.ezgg_lan_manager.types.Team import Team
from src.ezgg_lan_manager.types.User import User from src.ezgg_lan_manager.types.User import User
from src.ezgg_lan_manager.types.UserSession import UserSession
class TeamsPage(Component): class TeamsPage(Component):
@ -27,13 +26,10 @@ class TeamsPage(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} - Teams")
self.session[RefreshService].subscribe(self.on_populate)
self.all_teams = await self.session[TeamService].get_all_teams() self.all_teams = await self.session[TeamService].get_all_teams()
try: self.user = await self.session[UserService].get_user(self.session[SessionStorage].user_id)
self.user = await self.session[UserService].get_user(self.session[UserSession].user_id) await self.session.set_title(f"{self.session[ConfigurationService].get_lan_info().name} - Teams")
except KeyError: self.session[SessionStorage].subscribe_to_logged_in_or_out_event(str(self.__class__), self.on_populate)
self.user = None
async def on_join_button_pressed(self, team: Team) -> None: async def on_join_button_pressed(self, team: Team) -> None:
if self.user is None: if self.user is None:

View File

@ -1,36 +1,27 @@
import logging
from asyncio import sleep from asyncio import sleep
from functools import partial
from typing import Optional, Union, Literal from typing import Optional, Union, Literal
from from_root import from_root from from_root import from_root
from rio import Column, Component, event, TextStyle, Text, Row, Image, Spacer, ProgressCircle, Button, Checkbox, ThemeContextSwitcher, Link, Revealer, PointerEventListener, \ from rio import Column, Component, event, TextStyle, Text, Row, Image, Spacer, ProgressCircle, Button, Checkbox, ThemeContextSwitcher, Link, Revealer, PointerEventListener, \
PointerEvent, Rectangle, Color, Popup, Dropdown PointerEvent, Rectangle, Color
from src.ezgg_lan_manager import ConfigurationService, TournamentService, UserService, TicketingService, TeamService from src.ezgg_lan_manager import ConfigurationService, TournamentService, UserService, TicketingService
from src.ezgg_lan_manager.components.MainViewContentBox import MainViewContentBox from src.ezgg_lan_manager.components.MainViewContentBox import MainViewContentBox
from src.ezgg_lan_manager.components.TournamentDetailsInfoRow import TournamentDetailsInfoRow from src.ezgg_lan_manager.components.TournamentDetailsInfoRow import TournamentDetailsInfoRow
from src.ezgg_lan_manager.types.DateUtil import weekday_to_display_text from src.ezgg_lan_manager.types.DateUtil import weekday_to_display_text
from src.ezgg_lan_manager.types.Team import Team, TeamStatus from src.ezgg_lan_manager.types.SessionStorage import SessionStorage
from src.ezgg_lan_manager.types.Tournament import Tournament from src.ezgg_lan_manager.types.Tournament import Tournament
from src.ezgg_lan_manager.types.TournamentBase import TournamentStatus, tournament_status_to_display_text, tournament_format_to_display_texts, ParticipantType from src.ezgg_lan_manager.types.TournamentBase import TournamentStatus, tournament_status_to_display_text, tournament_format_to_display_texts
from src.ezgg_lan_manager.types.User import User from src.ezgg_lan_manager.types.User import User
from src.ezgg_lan_manager.types.UserSession import UserSession
logger = logging.getLogger(__name__.split(".")[-1])
class TournamentDetailsPage(Component): class TournamentDetailsPage(Component):
tournament: Optional[Union[Tournament, str]] = None tournament: Optional[Union[Tournament, str]] = None
rules_accepted: bool = False rules_accepted: bool = False
user: Optional[User] = None user: Optional[User] = None
user_teams: list[Team] = []
loading: bool = False loading: bool = False
participant_revealer_open: bool = False participant_revealer_open: bool = False
current_tournament_user_or_team_list: Union[list[User], list[Team]] = [] current_tournament_user_list: list[User] = [] # ToDo: Integrate Teams
team_dialog_open: bool = False
team_register_options: dict[str, Optional[Team]] = {"": None}
team_selected_for_register: Optional[Team] = None
# State for message above register button # State for message above register button
message: str = "" message: str = ""
@ -46,20 +37,11 @@ class TournamentDetailsPage(Component):
self.tournament = await self.session[TournamentService].get_tournament_by_id(tournament_id) self.tournament = await self.session[TournamentService].get_tournament_by_id(tournament_id)
if self.tournament is not None: if self.tournament is not None:
await self.session.set_title(f"{self.session[ConfigurationService].get_lan_info().name} - {self.tournament.name}") await self.session.set_title(f"{self.session[ConfigurationService].get_lan_info().name} - {self.tournament.name}")
if self.tournament.participant_type == ParticipantType.PLAYER: self.current_tournament_user_list = await self.session[TournamentService].get_users_from_participant_list(self.tournament.participants)
self.current_tournament_user_or_team_list = await self.session[TournamentService].get_users_from_participant_list(self.tournament.participants)
elif self.tournament.participant_type == ParticipantType.TEAM:
self.current_tournament_user_or_team_list = await self.session[TournamentService].get_teams_from_participant_list(self.tournament.participants)
else: else:
await self.session.set_title(f"{self.session[ConfigurationService].get_lan_info().name} - Turniere") await self.session.set_title(f"{self.session[ConfigurationService].get_lan_info().name} - Turniere")
try: self.user = await self.session[UserService].get_user(self.session[SessionStorage].user_id)
user_id = self.session[UserSession].user_id
self.user = await self.session[UserService].get_user(user_id)
self.user_teams = await self.session[TeamService].get_teams_for_user_by_id(user_id)
except KeyError:
self.user = None
self.user_teams = []
self.loading_done() self.loading_done()
@ -69,12 +51,7 @@ class TournamentDetailsPage(Component):
async def update(self) -> None: async def update(self) -> None:
self.tournament = await self.session[TournamentService].get_tournament_by_id(self.tournament.id) self.tournament = await self.session[TournamentService].get_tournament_by_id(self.tournament.id)
if self.tournament is None: self.current_tournament_user_list = await self.session[TournamentService].get_users_from_participant_list(self.tournament.participants)
return
if self.tournament.participant_type == ParticipantType.PLAYER:
self.current_tournament_user_or_team_list = await self.session[TournamentService].get_users_from_participant_list(self.tournament.participants)
elif self.tournament.participant_type == ParticipantType.TEAM:
self.current_tournament_user_or_team_list = await self.session[TournamentService].get_teams_from_participant_list(self.tournament.participants)
def open_close_participant_revealer(self, _: PointerEvent) -> None: def open_close_participant_revealer(self, _: PointerEvent) -> None:
self.participant_revealer_open = not self.participant_revealer_open self.participant_revealer_open = not self.participant_revealer_open
@ -89,73 +66,24 @@ class TournamentDetailsPage(Component):
self.is_success = False self.is_success = False
self.message = "Turnieranmeldung nur mit Ticket" self.message = "Turnieranmeldung nur mit Ticket"
else: else:
# Register single player
if self.tournament.participant_type == ParticipantType.PLAYER:
try: try:
await self.session[TournamentService].register_user_for_tournament(self.user.user_id, self.tournament.id) await self.session[TournamentService].register_user_for_tournament(self.user.user_id, self.tournament.id)
await self.artificial_delay() await self.artificial_delay()
self.is_success = True self.is_success = True
self.message = f"Erfolgreich angemeldet!" self.message = f"Erfolgreich angemeldet!"
except Exception as e: except Exception as e:
logger.error(e)
self.is_success = False self.is_success = False
self.message = f"Fehler: {e}" self.message = f"Fehler: {e}"
# Register team
elif self.tournament.participant_type == ParticipantType.TEAM:
try:
team_register_options = {"": None}
for team in self.user_teams:
if team.members[self.user] == TeamStatus.OFFICER or team.members[self.user] == TeamStatus.LEADER:
team_register_options[team.name] = team
if team_register_options:
self.team_register_options = team_register_options
else:
self.team_register_options = {"": None}
except StopIteration as e:
logger.error(f"Error trying to teams to register: {e}")
self.team_dialog_open = True
return # Model should handle loading state now
else:
pass
await self.update() await self.update()
self.loading = False self.loading = False
async def on_team_register_confirmed(self) -> None: async def unregister_pressed(self) -> None:
if self.team_selected_for_register is None:
await self.on_team_register_canceled()
return
try:
await self.session[TournamentService].register_team_for_tournament(self.team_selected_for_register.id, self.tournament.id)
await self.artificial_delay()
self.is_success = True
self.message = f"Erfolgreich angemeldet!"
self.team_dialog_open = False
self.team_selected_for_register = None
except Exception as e:
logger.error(e)
self.message = f"Fehler: {e}"
self.is_success = False
await self.update()
self.loading = False
async def on_team_register_canceled(self) -> None:
self.team_dialog_open = False
self.team_selected_for_register = None
self.loading = False
async def unregister_pressed(self, team: Optional[Team] = None) -> None:
self.loading = True self.loading = True
if not self.user: if not self.user:
return return
try: try:
if self.tournament.participant_type == ParticipantType.PLAYER:
await self.session[TournamentService].unregister_user_from_tournament(self.user.user_id, self.tournament.id) await self.session[TournamentService].unregister_user_from_tournament(self.user.user_id, self.tournament.id)
elif self.tournament.participant_type == ParticipantType.TEAM:
if team.members[self.user] == TeamStatus.OFFICER or team.members[self.user] == TeamStatus.LEADER:
await self.session[TournamentService].unregister_team_from_tournament(team.id, self.tournament.id)
else:
raise PermissionError("Nur Leiter und Offiziere können das Team abmelden")
await self.artificial_delay() await self.artificial_delay()
self.is_success = True self.is_success = True
self.message = f"Erfolgreich abgemeldet!" self.message = f"Erfolgreich abgemeldet!"
@ -171,6 +99,7 @@ class TournamentDetailsPage(Component):
def loading_done(self) -> None: def loading_done(self) -> None:
if self.tournament is None: if self.tournament is None:
self.tournament = "Turnier konnte nicht gefunden werden" self.tournament = "Turnier konnte nicht gefunden werden"
self.session[SessionStorage].subscribe_to_logged_in_or_out_event(str(self.__class__), self.on_populate)
def build(self) -> Component: def build(self) -> Component:
if self.tournament is None: if self.tournament is None:
@ -216,12 +145,10 @@ class TournamentDetailsPage(Component):
on_press=self.tree_button_clicked on_press=self.tree_button_clicked
) )
# ToDo: Integrate Teams logic
ids_of_participants = [p.id for p in self.tournament.participants] ids_of_participants = [p.id for p in self.tournament.participants]
color_key: Literal["hud", "danger"] = "hud" color_key: Literal["hud", "danger"] = "hud"
on_press_function = self.register_pressed on_press_function = self.register_pressed
if self.tournament.participant_type == ParticipantType.PLAYER:
self.current_tournament_user_or_team_list: list[User] # IDE TypeHint
participant_names = "\n".join([u.user_name for u in self.current_tournament_user_or_team_list])
if self.user and self.user.user_id in ids_of_participants: # User already registered for tournament if self.user and self.user.user_id in ids_of_participants: # User already registered for tournament
button_text = "Abmelden" button_text = "Abmelden"
button_sensitive_hook = True # User has already accepted the rules previously button_sensitive_hook = True # User has already accepted the rules previously
@ -234,29 +161,6 @@ class TournamentDetailsPage(Component):
# This should NEVER happen # This should NEVER happen
button_text = "Anmelden" button_text = "Anmelden"
button_sensitive_hook = False button_sensitive_hook = False
elif self.tournament.participant_type == ParticipantType.TEAM:
self.current_tournament_user_or_team_list: list[Team] # IDE TypeHint
participant_names = "\n".join([t.name for t in self.current_tournament_user_or_team_list])
user_team_registered = []
for team in self.user_teams:
if team.id in ids_of_participants:
user_team_registered.append(team)
if self.user and len(user_team_registered) > 0: # Any of the users teams already registered for tournament
button_text = f"{user_team_registered[0].abbreviation} abmelden"
button_sensitive_hook = True # User has already accepted the rules previously
color_key = "danger"
on_press_function = partial(self.unregister_pressed, user_team_registered[0])
elif self.user and len(user_team_registered) == 0:
button_text = "Anmelden"
button_sensitive_hook = self.rules_accepted
else:
# This should NEVER happen
button_text = "Anmelden"
button_sensitive_hook = False
else:
logger.fatal("Did someone add new values to ParticipantType ? ;)")
return Column()
if self.tournament.status != TournamentStatus.OPEN or self.tournament.is_full: if self.tournament.status != TournamentStatus.OPEN or self.tournament.is_full:
button_sensitive_hook = False # Override button controls if tournament is not open anymore or full button_sensitive_hook = False # Override button controls if tournament is not open anymore or full
@ -282,6 +186,8 @@ class TournamentDetailsPage(Component):
# No UI here if user not logged in # No UI here if user not logged in
accept_rules_row, button = Spacer(), Spacer() accept_rules_row, button = Spacer(), Spacer()
content = Column( content = Column(
Row( Row(
Image(image=from_root(f"src/ezgg_lan_manager/assets/img/games/{self.tournament.game_title.image_name}"), margin_right=1), Image(image=from_root(f"src/ezgg_lan_manager/assets/img/games/{self.tournament.game_title.image_name}"), margin_right=1),
@ -307,7 +213,7 @@ class TournamentDetailsPage(Component):
content=Rectangle( content=Rectangle(
content=TournamentDetailsInfoRow( content=TournamentDetailsInfoRow(
"Teilnehmer ▴" if self.participant_revealer_open else "Teilnehmer ▾", "Teilnehmer ▴" if self.participant_revealer_open else "Teilnehmer ▾",
f"{len(self.current_tournament_user_or_team_list)} / {self.tournament.max_participants}", f"{len(self.current_tournament_user_list)} / {self.tournament.max_participants}",
value_color=self.session.theme.danger_color if self.tournament.is_full else self.session.theme.background_color, value_color=self.session.theme.danger_color if self.tournament.is_full else self.session.theme.background_color,
key_color=self.session.theme.secondary_color key_color=self.session.theme.secondary_color
), ),
@ -319,7 +225,7 @@ class TournamentDetailsPage(Component):
Revealer( Revealer(
header=None, header=None,
content=Text( content=Text(
participant_names, "\n".join([u.user_name for u in self.current_tournament_user_list]), # ToDo: Integrate Teams
style=TextStyle(fill=self.session.theme.background_color) style=TextStyle(fill=self.session.theme.background_color)
), ),
is_open=self.participant_revealer_open, is_open=self.participant_revealer_open,
@ -349,39 +255,6 @@ class TournamentDetailsPage(Component):
button button
) )
if self.tournament and self.tournament.participant_type == ParticipantType.TEAM:
content = Popup(
anchor=content,
content=Rectangle(
content=Column(
Text("Welches Team anmelden?", style=TextStyle(fill=self.session.theme.background_color, font_size=1.2), justify="center", margin_bottom=1),
ThemeContextSwitcher(
content=Dropdown(
options=self.team_register_options,
min_width=20,
selected_value=self.bind().team_selected_for_register
),
color=self.session.theme.hud_color,
margin_bottom=1
),
Row(
Button(content="Abbrechen", shape="rectangle", grow_x=False, on_press=self.on_team_register_canceled),
Button(content="Anmelden", shape="rectangle", grow_x=False, on_press=self.on_team_register_confirmed),
spacing=1
),
margin=0.5
),
min_width=30,
min_height=4,
fill=self.session.theme.primary_color,
margin_top=3.5,
stroke_width=0.3,
stroke_color=self.session.theme.neutral_color,
),
is_open=self.team_dialog_open,
color="none"
)
return Column( return Column(
MainViewContentBox( MainViewContentBox(
Column( Column(

View File

@ -62,8 +62,7 @@ class DatabaseService:
password=self._database_config.db_password, password=self._database_config.db_password,
db=self._database_config.db_name, db=self._database_config.db_name,
minsize=1, minsize=1,
maxsize=40, maxsize=40
autocommit=True
) )
except aiomysql.OperationalError: except aiomysql.OperationalError:
return False return False
@ -76,15 +75,14 @@ class DatabaseService:
user_name=data[1], user_name=data[1],
user_mail=data[2], user_mail=data[2],
user_password=data[3], user_password=data[3],
user_fallback_password=data[4], user_first_name=data[4],
user_first_name=data[5], user_last_name=data[5],
user_last_name=data[6], user_birth_day=data[6],
user_birth_day=data[7], is_active=bool(data[7]),
is_active=bool(data[8]), is_team_member=bool(data[8]),
is_team_member=bool(data[9]), is_admin=bool(data[9]),
is_admin=bool(data[10]), created_at=data[10],
created_at=data[11], last_updated_at=data[11]
last_updated_at=data[12]
) )
@staticmethod @staticmethod
@ -187,10 +185,10 @@ class DatabaseService:
async with conn.cursor(aiomysql.Cursor) as cursor: async with conn.cursor(aiomysql.Cursor) as cursor:
try: try:
await cursor.execute( await cursor.execute(
"UPDATE users SET user_name=%s, user_mail=%s, user_password=%s, user_fallback_password=%s," "UPDATE users SET user_name=%s, user_mail=%s, user_password=%s, user_first_name=%s, "
"user_first_name=%s, user_last_name=%s, user_birth_date=%s, is_active=%s, is_team_member=%s," "user_last_name=%s, user_birth_date=%s, is_active=%s, is_team_member=%s, is_admin=%s "
" is_admin=%s WHERE (user_id=%s)", "WHERE (user_id=%s)",
(user.user_name, user.user_mail.lower(), user.user_password, user.user_fallback_password, (user.user_name, user.user_mail.lower(), user.user_password,
user.user_first_name, user.user_last_name, user.user_birth_day, user.user_first_name, user.user_last_name, user.user_birth_day,
user.is_active, user.is_team_member, user.is_admin, user.is_active, user.is_team_member, user.is_admin,
user.user_id) user.user_id)
@ -861,7 +859,6 @@ class DatabaseService:
t.status AS tournament_status, t.status AS tournament_status,
t.max_participants, t.max_participants,
t.created_at, t.created_at,
t.participant_type AS tournament_participant_type,
/* ======================= /* =======================
Game Title Game Title
@ -877,7 +874,6 @@ class DatabaseService:
======================= */ ======================= */
tp.id AS participant_id, tp.id AS participant_id,
tp.user_id, tp.user_id,
tp.team_id,
tp.participant_type, tp.participant_type,
tp.seed, tp.seed,
tp.joined_at tp.joined_at
@ -911,8 +907,6 @@ class DatabaseService:
if current_tournament is None or current_tournament.id != row["tournament_id"]: if current_tournament is None or current_tournament.id != row["tournament_id"]:
if current_tournament is not None: if current_tournament is not None:
tournaments.append(current_tournament) tournaments.append(current_tournament)
participant_type = self._parse_participant_type(row["tournament_participant_type"])
id_accessor = "user_id" if participant_type == ParticipantType.PLAYER else "team_id"
current_tournament = Tournament( current_tournament = Tournament(
id_=row["tournament_id"], id_=row["tournament_id"],
name=row["tournament_name"], name=row["tournament_name"],
@ -926,16 +920,14 @@ class DatabaseService:
format_=self._parse_tournament_format(row["tournament_format"]), format_=self._parse_tournament_format(row["tournament_format"]),
start_time=row["start_time"], start_time=row["start_time"],
status=self._parse_tournament_status(row["tournament_status"]), status=self._parse_tournament_status(row["tournament_status"]),
participants=[Participant(id_=row[id_accessor], participant_type=self._parse_participant_type(row["participant_type"]))] if row[id_accessor] is not None else [], participants=[Participant(id_=row["user_id"], participant_type=self._parse_participant_type(row["participant_type"]))] if row["user_id"] is not None else [],
matches=None, # ToDo: Implement matches=None, # ToDo: Implement
rounds=[], # ToDo: Implement rounds=[], # ToDo: Implement
max_participants=row["max_participants"], max_participants=row["max_participants"]
participant_type=participant_type
) )
else: else:
id_accessor = "user_id" if current_tournament.participant_type == ParticipantType.PLAYER else "team_id"
current_tournament.add_participant( current_tournament.add_participant(
Participant(id_=row[id_accessor], participant_type=self._parse_participant_type(row["participant_type"])) Participant(id_=row["user_id"], participant_type=self._parse_participant_type(row["participant_type"]))
) )
else: else:
tournaments.append(current_tournament) tournaments.append(current_tournament)
@ -943,14 +935,11 @@ class DatabaseService:
return tournaments return tournaments
async def add_participant_to_tournament(self, participant: Participant, tournament: Tournament) -> None: async def add_participant_to_tournament(self, participant: Participant, tournament: Tournament) -> None:
if participant.participant_type != tournament.participant_type:
raise ValueError(f"Can not add {participant.participant_type.name} to {tournament.participant_type.name} tournament")
accessor = "user_id" if participant.participant_type == ParticipantType.PLAYER else "team_id"
async with self._connection_pool.acquire() as conn: async with self._connection_pool.acquire() as conn:
async with conn.cursor(aiomysql.Cursor) as cursor: async with conn.cursor(aiomysql.Cursor) as cursor:
try: try:
await cursor.execute( await cursor.execute(
f"INSERT INTO tournament_participants (tournament_id, {accessor}, participant_type) VALUES (%s, %s, %s);", "INSERT INTO tournament_participants (tournament_id, user_id, participant_type) VALUES (%s, %s, %s);",
(tournament.id, participant.id, participant.participant_type.name) (tournament.id, participant.id, participant.participant_type.name)
) )
await conn.commit() await conn.commit()
@ -963,12 +952,11 @@ class DatabaseService:
logger.warning(f"Error adding participant to tournament: {e}") logger.warning(f"Error adding participant to tournament: {e}")
async def remove_participant_from_tournament(self, participant: Participant, tournament: Tournament) -> None: async def remove_participant_from_tournament(self, participant: Participant, tournament: Tournament) -> None:
accessor = "user_id" if participant.participant_type == ParticipantType.PLAYER else "team_id"
async with self._connection_pool.acquire() as conn: async with self._connection_pool.acquire() as conn:
async with conn.cursor(aiomysql.Cursor) as cursor: async with conn.cursor(aiomysql.Cursor) as cursor:
try: try:
await cursor.execute( await cursor.execute(
f"DELETE FROM tournament_participants WHERE (tournament_id = %s AND {accessor} = %s);", "DELETE FROM tournament_participants WHERE (tournament_id = %s AND user_id = %s);",
(tournament.id, participant.id) (tournament.id, participant.id)
) )
await conn.commit() await conn.commit()

View File

@ -3,20 +3,20 @@ from typing import Optional
from rio import UserSettings from rio import UserSettings
from src.ezgg_lan_manager.types.UserSession import UserSession from src.ezgg_lan_manager.types.SessionStorage import SessionStorage
class LocalData(UserSettings): class LocalData(UserSettings):
stored_session_token: Optional[str] stored_session_token: Optional[str] = None
class LocalDataService: class LocalDataService:
def __init__(self) -> None: def __init__(self) -> None:
self._session: dict[str, UserSession] = {} self._session: dict[str, SessionStorage] = {}
def verify_token(self, token: str) -> Optional[UserSession]: def verify_token(self, token: str) -> Optional[SessionStorage]:
return self._session.get(token) return self._session.get(token)
def set_session(self, session: UserSession) -> str: def set_session(self, session: SessionStorage) -> str:
key = secrets.token_hex(32) key = secrets.token_hex(32)
self._session[key] = session self._session[key] = session
return key return key

View File

@ -1,17 +0,0 @@
from typing import Callable
class RefreshService:
"""
rio.Components can subscribe to this service with their on_populate method.
Those methods get called whenever a overall refresh is needed. Usually when the user logs in or out.
"""
def __init__(self) -> None:
self.subscribers: set[Callable] = set()
def subscribe(self, refresh_cb: Callable) -> None:
self.subscribers.add(refresh_cb)
async def trigger_refresh(self) -> None:
for refresh_cb in self.subscribers:
await refresh_cb()

View File

@ -3,7 +3,6 @@ from typing import Optional
from src.ezgg_lan_manager.services.DatabaseService import DatabaseService from src.ezgg_lan_manager.services.DatabaseService import DatabaseService
from src.ezgg_lan_manager.services.UserService import UserService from src.ezgg_lan_manager.services.UserService import UserService
from src.ezgg_lan_manager.types.Participant import Participant from src.ezgg_lan_manager.types.Participant import Participant
from src.ezgg_lan_manager.types.Team import Team
from src.ezgg_lan_manager.types.Tournament import Tournament from src.ezgg_lan_manager.types.Tournament import Tournament
from src.ezgg_lan_manager.types.TournamentBase import ParticipantType, TournamentError from src.ezgg_lan_manager.types.TournamentBase import ParticipantType, TournamentError
from src.ezgg_lan_manager.types.User import User from src.ezgg_lan_manager.types.User import User
@ -28,24 +27,11 @@ class TournamentService:
tournament = await self.get_tournament_by_id(tournament_id) tournament = await self.get_tournament_by_id(tournament_id)
if not tournament: if not tournament:
raise TournamentError(f"No tournament with ID {tournament_id} was found") raise TournamentError(f"No tournament with ID {tournament_id} was found")
if tournament.participant_type != ParticipantType.PLAYER:
raise TournamentError(f"Can only add single player to team tournament, not {tournament.participant_type.name}")
participant = Participant(id_=user_id, participant_type=ParticipantType.PLAYER) participant = Participant(id_=user_id, participant_type=ParticipantType.PLAYER)
tournament.add_participant(participant) tournament.add_participant(participant)
await self._db_service.add_participant_to_tournament(participant, tournament) await self._db_service.add_participant_to_tournament(participant, tournament)
self._cache_dirty = True self._cache_dirty = True
async def register_team_for_tournament(self, team_id: int, tournament_id: int) -> None:
tournament = await self.get_tournament_by_id(tournament_id)
if not tournament:
raise TournamentError(f"No tournament with ID {tournament_id} was found")
if tournament.participant_type != ParticipantType.TEAM:
raise TournamentError(f"Can only add team to team tournament, not {tournament.participant_type.name}")
participant = Participant(id_=team_id, participant_type=ParticipantType.TEAM)
tournament.add_participant(participant)
await self._db_service.add_participant_to_tournament(participant, tournament)
self._cache_dirty = True
async def unregister_user_from_tournament(self, user_id: int, tournament_id: int) -> None: async def unregister_user_from_tournament(self, user_id: int, tournament_id: int) -> None:
tournament = await self.get_tournament_by_id(tournament_id) tournament = await self.get_tournament_by_id(tournament_id)
if not tournament: if not tournament:
@ -56,16 +42,6 @@ class TournamentService:
await self._db_service.remove_participant_from_tournament(participant, tournament) await self._db_service.remove_participant_from_tournament(participant, tournament)
self._cache_dirty = True self._cache_dirty = True
async def unregister_team_from_tournament(self, team_id: int, tournament_id: int) -> None:
tournament = await self.get_tournament_by_id(tournament_id)
if not tournament:
raise TournamentError(f"No tournament with ID {tournament_id} was found")
participant = next(filter(lambda p: p.id == team_id, tournament.participants), None)
if participant is not None:
tournament.remove_participant(participant)
await self._db_service.remove_participant_from_tournament(participant, tournament)
self._cache_dirty = True
async def get_tournaments(self) -> list[Tournament]: async def get_tournaments(self) -> list[Tournament]:
if self._cache_dirty: if self._cache_dirty:
await self._update_cache() await self._update_cache()
@ -81,11 +57,6 @@ class TournamentService:
participant_ids = [p.id for p in participants] participant_ids = [p.id for p in participants]
return list(filter(lambda u: u.user_id in participant_ids, all_users)) return list(filter(lambda u: u.user_id in participant_ids, all_users))
async def get_teams_from_participant_list(self, participants: list[Participant]) -> list[Team]:
all_teams = await self._db_service.get_teams()
participant_ids = [p.id for p in participants]
return list(filter(lambda t: t.id in participant_ids, all_teams))
async def start_tournament(self, tournament_id: int): async def start_tournament(self, tournament_id: int):
tournament = await self.get_tournament_by_id(tournament_id) tournament = await self.get_tournament_by_id(tournament_id)
if tournament: if tournament:

View File

@ -59,12 +59,9 @@ class UserService:
async 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 = await self.get_user(user_name_or_mail) user = await self.get_user(user_name_or_mail)
user_password_hash = sha256(password_clear_text.encode(encoding="utf-8")).hexdigest()
if not user: if not user:
return False return False
if user.user_fallback_password and user.user_fallback_password == user_password_hash: return user.user_password == sha256(password_clear_text.encode(encoding="utf-8")).hexdigest()
return True
return user.user_password == user_password_hash
def _check_for_disallowed_char(self, name: str) -> Optional[str]: def _check_for_disallowed_char(self, name: str) -> Optional[str]:

View File

@ -0,0 +1,36 @@
import logging
from collections.abc import Callable
from dataclasses import dataclass, field
from typing import Optional
logger = logging.getLogger(__name__.split(".")[-1])
# ToDo: Persist between reloads: https://rio.dev/docs/howto/persistent-settings
# Note for ToDo: rio.UserSettings are saved LOCALLY, do not just read a user_id here!
@dataclass(frozen=False)
class SessionStorage:
_user_id: Optional[int] = None # DEBUG: Put user ID here to skip login
_is_team_member: bool = False
_notification_callbacks: dict[str, Callable] = field(default_factory=dict)
async def clear(self) -> None:
await self.set_user_id_and_team_member_flag(None, False)
def subscribe_to_logged_in_or_out_event(self, component_id: str, callback: Callable) -> None:
self._notification_callbacks[component_id] = callback
@property
def user_id(self) -> Optional[int]:
return self._user_id
@property
def is_team_member(self) -> bool:
return self._is_team_member
async def set_user_id_and_team_member_flag(self, user_id: Optional[int], is_team_member: bool) -> None:
self._user_id = user_id
self._is_team_member = is_team_member
for component_id, callback in self._notification_callbacks.items():
logger.debug(f"Calling logged in callback from {component_id}")
await callback()

View File

@ -27,11 +27,3 @@ class Team:
abbreviation: str abbreviation: str
members: dict[User, TeamStatus] members: dict[User, TeamStatus]
join_password: str join_password: str
def __hash__(self) -> int:
return hash(self.id)
def __eq__(self, other):
if not isinstance(other, Team):
return NotImplemented
return self.id == other.id

View File

@ -5,7 +5,7 @@ from math import ceil, log2
from src.ezgg_lan_manager.types.Match import Match, FFAMatch from src.ezgg_lan_manager.types.Match import Match, FFAMatch
from src.ezgg_lan_manager.types.Participant import Participant from src.ezgg_lan_manager.types.Participant import Participant
from src.ezgg_lan_manager.types.TournamentBase import GameTitle, TournamentFormat, TournamentStatus, TournamentError, Bracket, MatchStatus, ParticipantType from src.ezgg_lan_manager.types.TournamentBase import GameTitle, TournamentFormat, TournamentStatus, TournamentError, Bracket, MatchStatus
class Tournament: class Tournament:
@ -20,8 +20,7 @@ class Tournament:
participants: list[Participant], participants: list[Participant],
matches: Optional[tuple[Match]], matches: Optional[tuple[Match]],
rounds: list[list[Match]], rounds: list[list[Match]],
max_participants: int, max_participants: int) -> None:
participant_type: ParticipantType) -> None:
self._id = id_ self._id = id_
self._name = name self._name = name
self._description = description self._description = description
@ -33,7 +32,6 @@ class Tournament:
self._matches = matches self._matches = matches
self._rounds = rounds self._rounds = rounds
self._max_participants = max_participants self._max_participants = max_participants
self._participant_type = participant_type
@property @property
def id(self) -> int: def id(self) -> int:
@ -87,10 +85,6 @@ class Tournament:
def is_full(self) -> bool: def is_full(self) -> bool:
return len(self._participants) >= self._max_participants return len(self._participants) >= self._max_participants
@property
def participant_type(self) -> ParticipantType:
return self._participant_type
def add_participant(self, participant: Participant) -> None: def add_participant(self, participant: Participant) -> None:
if participant.id in (p.id for p in self._participants): if participant.id in (p.id for p in self._participants):
raise TournamentError(f"Participant with ID {participant.id} already registered for tournament") raise TournamentError(f"Participant with ID {participant.id} already registered for tournament")

View File

@ -9,7 +9,6 @@ class User:
user_name: str user_name: str
user_mail: str user_mail: str
user_password: str user_password: str
user_fallback_password: Optional[str]
user_first_name: Optional[str] user_first_name: Optional[str]
user_last_name: Optional[str] user_last_name: Optional[str]
user_birth_day: Optional[date] user_birth_day: Optional[date]

View File

@ -1,9 +0,0 @@
from dataclasses import dataclass
from uuid import UUID
@dataclass
class UserSession:
id: UUID
user_id: int
is_team_member: bool