update rio and fix broken user sessions (#5)

Co-authored-by: David Rodenkirchen <davidr.develop@gmail.com>
Reviewed-on: Vereins-IT/ez-lan-manager#5
This commit is contained in:
David Rodenkirchen 2025-02-01 23:43:21 +00:00
parent f0f8a08f87
commit 6ff7adb165
18 changed files with 87 additions and 31 deletions

Binary file not shown.

View File

@ -11,6 +11,7 @@ from from_root import from_root
from src.ez_lan_manager import pages, init_services from src.ez_lan_manager import pages, init_services
from src.ez_lan_manager.helpers.LoggedInGuard import logged_in_guard, not_logged_in_guard, team_guard from src.ez_lan_manager.helpers.LoggedInGuard import logged_in_guard, not_logged_in_guard, team_guard
from src.ez_lan_manager.services.DatabaseService import NoDatabaseConnectionError from src.ez_lan_manager.services.DatabaseService import NoDatabaseConnectionError
from src.ez_lan_manager.services.LocalDataService import LocalData
from src.ez_lan_manager.types.SessionStorage import SessionStorage from src.ez_lan_manager.types.SessionStorage import SessionStorage
logger = logging.getLogger("EzLanManager") logger = logging.getLogger("EzLanManager")
@ -29,16 +30,17 @@ if __name__ == "__main__":
corner_radius_large=0, corner_radius_large=0,
font=Font(from_root("src/ez_lan_manager/assets/fonts/joystix.otf")) font=Font(from_root("src/ez_lan_manager/assets/fonts/joystix.otf"))
) )
services = init_services() default_attachments = [LocalData()]
default_attachments.extend(init_services())
lan_info = services[2].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:
await session.set_title(lan_info.name) await session.set_title(lan_info.name)
session.attach(SessionStorage()) session.attach(SessionStorage())
async def on_app_start(a: App) -> None: async def on_app_start(a: App) -> None:
init_result = await a.default_attachments[3].init_db_pool() init_result = await a.default_attachments[4].init_db_pool()
if not init_result: if not init_result:
logger.fatal("Could not connect to database, exiting...") logger.fatal("Could not connect to database, exiting...")
sys.exit(1) sys.exit(1)
@ -163,7 +165,7 @@ if __name__ == "__main__":
], ],
theme=theme, theme=theme,
assets_dir=Path(__file__).parent / "assets", assets_dir=Path(__file__).parent / "assets",
default_attachments=services, default_attachments=default_attachments,
on_session_start=on_session_start, on_session_start=on_session_start,
on_app_start=on_app_start, on_app_start=on_app_start,
icon=from_root("src/ez_lan_manager/assets/img/favicon.png"), icon=from_root("src/ez_lan_manager/assets/img/favicon.png"),

View File

@ -7,6 +7,7 @@ from src.ez_lan_manager.services.AccountingService import AccountingService
from src.ez_lan_manager.services.CateringService import CateringService from src.ez_lan_manager.services.CateringService import CateringService
from src.ez_lan_manager.services.ConfigurationService import ConfigurationService from src.ez_lan_manager.services.ConfigurationService import ConfigurationService
from src.ez_lan_manager.services.DatabaseService import DatabaseService from src.ez_lan_manager.services.DatabaseService import DatabaseService
from src.ez_lan_manager.services.LocalDataService import LocalDataService
from src.ez_lan_manager.services.MailingService import MailingService from src.ez_lan_manager.services.MailingService import MailingService
from src.ez_lan_manager.services.NewsService import NewsService from src.ez_lan_manager.services.NewsService import NewsService
from src.ez_lan_manager.services.SeatingService import SeatingService from src.ez_lan_manager.services.SeatingService import SeatingService
@ -15,7 +16,7 @@ from src.ez_lan_manager.services.UserService import UserService
from src.ez_lan_manager.types import * from src.ez_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]: def init_services() -> tuple[AccountingService, CateringService, ConfigurationService, DatabaseService, MailingService, NewsService, SeatingService, TicketingService, UserService, LocalDataService]:
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())
@ -26,5 +27,6 @@ def init_services() -> tuple[AccountingService, CateringService, ConfigurationSe
ticketing_service = TicketingService(configuration_service.get_ticket_info(), db_service, accounting_service) ticketing_service = TicketingService(configuration_service.get_ticket_info(), db_service, accounting_service)
seating_service = SeatingService(configuration_service.get_seating_configuration(), configuration_service.get_lan_info(), db_service, ticketing_service) seating_service = SeatingService(configuration_service.get_seating_configuration(), configuration_service.get_lan_info(), db_service, ticketing_service)
catering_service = CateringService(db_service, accounting_service, user_service) catering_service = CateringService(db_service, accounting_service, user_service)
local_data_service = LocalDataService()
return accounting_service, catering_service, configuration_service, db_service, mailing_service, news_service, seating_service, ticketing_service, user_service return accounting_service, catering_service, configuration_service, db_service, mailing_service, news_service, seating_service, ticketing_service, user_service, local_data_service

View File

@ -21,7 +21,7 @@ class AnimatedText(Component):
) )
for c in text: for c in text:
self.text_comp.text = self.text_comp.text + c self.text_comp.text = self.text_comp.text + c
await self.text_comp.force_refresh() self.text_comp.force_refresh()
await sleep(speed) await sleep(speed)
else: else:
self.text_comp.style = TextStyle( self.text_comp.style = TextStyle(
@ -30,7 +30,7 @@ class AnimatedText(Component):
) )
for c in text: for c in text:
self.text_comp.text = self.text_comp.text + c self.text_comp.text = self.text_comp.text + c
await self.text_comp.force_refresh() self.text_comp.force_refresh()
await sleep(speed) await sleep(speed)
self._display_printing[0] = False self._display_printing[0] = False

View File

@ -1,21 +1,38 @@
from copy import copy, deepcopy from copy import copy, deepcopy
from typing import Optional from typing import Optional, Callable
from rio import * from rio import *
from src.ez_lan_manager import ConfigurationService, UserService from src.ez_lan_manager import ConfigurationService, UserService, LocalDataService
from src.ez_lan_manager.components.DesktopNavigationButton import DesktopNavigationButton from src.ez_lan_manager.components.DesktopNavigationButton import DesktopNavigationButton
from src.ez_lan_manager.components.UserInfoAndLoginBox import UserInfoAndLoginBox from src.ez_lan_manager.components.UserInfoAndLoginBox import UserInfoAndLoginBox
from src.ez_lan_manager.services.LocalDataService import LocalData
from src.ez_lan_manager.types.SessionStorage import SessionStorage from src.ez_lan_manager.types.SessionStorage import SessionStorage
from src.ez_lan_manager.types.User import User from src.ez_lan_manager.types.User import User
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 async_init(self) -> None: async def async_init(self) -> None:
self.session[SessionStorage].subscribe_to_logged_in_or_out_event(str(self.__class__), self.async_init) 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:
# Hack-around, maybe fix in the future
self.force_login_box_refresh[-1]()
except IndexError:
pass
return
if self.session[SessionStorage].user_id: if self.session[SessionStorage].user_id:
self.user = await self.session[UserService].get_user(self.session[SessionStorage].user_id) self.user = await self.session[UserService].get_user(self.session[SessionStorage].user_id)
else: else:
@ -23,6 +40,8 @@ class DesktopNavigation(Component):
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()
self.force_login_box_refresh.append(user_info_and_login_box.force_refresh)
user_navigation = [ user_navigation = [
DesktopNavigationButton("News", "./news"), DesktopNavigationButton("News", "./news"),
Spacer(min_height=1), Spacer(min_height=1),
@ -62,7 +81,7 @@ class DesktopNavigation(Component):
Column( Column(
Text(lan_info.name, align_x=0.5, margin_top=0.3, style=TextStyle(fill=self.session.theme.hud_color, font_size=2.5)), Text(lan_info.name, align_x=0.5, margin_top=0.3, style=TextStyle(fill=self.session.theme.hud_color, font_size=2.5)),
Text(f"Edition {lan_info.iteration}", align_x=0.5, style=TextStyle(fill=self.session.theme.hud_color, font_size=1.2), margin_top=0.3, margin_bottom=2), Text(f"Edition {lan_info.iteration}", align_x=0.5, style=TextStyle(fill=self.session.theme.hud_color, font_size=1.2), margin_top=0.3, margin_bottom=2),
UserInfoAndLoginBox(), user_info_and_login_box,
*nav_to_use, *nav_to_use,
align_y=0 align_y=0
), ),

View File

@ -1,6 +1,7 @@
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 EventHandler
from src.ez_lan_manager.services.LocalDataService import LocalDataService, LocalData
from src.ez_lan_manager.services.UserService import UserService from src.ez_lan_manager.services.UserService import UserService
from src.ez_lan_manager.types.SessionStorage import SessionStorage from src.ez_lan_manager.types.SessionStorage import SessionStorage
from src.ez_lan_manager.types.User import User from src.ez_lan_manager.types.User import User
@ -27,13 +28,16 @@ class LoginBox(Component):
self.login_button_is_loading = False self.login_button_is_loading = False
self.is_account_locked = False self.is_account_locked = False
await self.session[SessionStorage].set_user_id_and_team_member_flag(user.user_id, user.is_team_member) await self.session[SessionStorage].set_user_id_and_team_member_flag(user.user_id, user.is_team_member)
await self.status_change_cb() token = self.session[LocalDataService].set_session(self.session[SessionStorage])
self.session[LocalData].stored_session_token = token
self.session.attach(self.session[LocalData])
self.status_change_cb()
else: else:
self.user_name_input_is_valid = False self.user_name_input_is_valid = False
self.password_input_is_valid = False self.password_input_is_valid = False
self.login_button_is_loading = False self.login_button_is_loading = False
self.is_account_locked = False self.is_account_locked = False
await self.force_refresh() self.force_refresh()
def build(self) -> Component: def build(self) -> Component:
user_name_input = TextInput( user_name_input = TextInput(

View File

@ -33,11 +33,11 @@ class ShoppingCartAndOrders(Component):
except IndexError: except IndexError:
return return
catering_service.save_cart(user_id, cart) catering_service.save_cart(user_id, cart)
await self.force_refresh() self.force_refresh()
async def on_empty_cart_pressed(self) -> None: async def on_empty_cart_pressed(self) -> None:
self.session[CateringService].save_cart(self.session[SessionStorage].user_id, []) self.session[CateringService].save_cart(self.session[SessionStorage].user_id, [])
await 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]
@ -48,20 +48,20 @@ class ShoppingCartAndOrders(Component):
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)
cart.append(item_to_add) cart.append(item_to_add)
catering_service.save_cart(user_id, cart) catering_service.save_cart(user_id, cart)
await self.force_refresh() self.force_refresh()
async def show_popup(self, text: str, is_error: bool) -> None: async def show_popup(self, text: str, is_error: bool) -> None:
self.popup_is_error = is_error self.popup_is_error = is_error
self.popup_message = text self.popup_message = text
self.popup_is_shown = True self.popup_is_shown = True
await self.force_refresh() self.force_refresh()
await sleep(POPUP_CLOSE_TIMEOUT_SECONDS) await sleep(POPUP_CLOSE_TIMEOUT_SECONDS)
self.popup_is_shown = False self.popup_is_shown = False
await self.force_refresh() self.force_refresh()
async def on_order_pressed(self) -> None: async def on_order_pressed(self) -> None:
self.order_button_loading = True self.order_button_loading = True
await self.force_refresh() self.force_refresh()
user_id = self.session[SessionStorage].user_id user_id = self.session[SessionStorage].user_id
cart = self.session[CateringService].get_cart(user_id) cart = self.session[CateringService].get_cart(user_id)

View File

@ -4,6 +4,7 @@ from typing import Optional
from rio import Component, TextStyle, Color, Button, Text, Rectangle, Column, Row, Spacer, Link, event, EventHandler from rio import Component, TextStyle, Color, Button, Text, Rectangle, Column, Row, Spacer, Link, event, EventHandler
from src.ez_lan_manager.components.UserInfoBoxButton import UserInfoBoxButton from src.ez_lan_manager.components.UserInfoBoxButton import UserInfoBoxButton
from src.ez_lan_manager.services.LocalDataService import LocalData, LocalDataService
from src.ez_lan_manager.services.UserService import UserService from src.ez_lan_manager.services.UserService import UserService
from src.ez_lan_manager.services.AccountingService import AccountingService from src.ez_lan_manager.services.AccountingService import AccountingService
from src.ez_lan_manager.services.TicketingService import TicketingService from src.ez_lan_manager.services.TicketingService import TicketingService
@ -52,7 +53,10 @@ class UserInfoBox(Component):
async def logout(self) -> None: async def logout(self) -> None:
await self.session[SessionStorage].clear() await self.session[SessionStorage].clear()
self.user = None self.user = None
await self.status_change_cb() self.session[LocalDataService].del_session(self.session[LocalData].stored_session_token)
self.session[LocalData].stored_session_token = None
self.session.attach(self.session[LocalData])
self.status_change_cb()
self.session.navigate_to("/") self.session.navigate_to("/")
@event.on_populate @event.on_populate

View File

@ -18,7 +18,7 @@ class BasePage(Component):
@event.on_window_size_change @event.on_window_size_change
async def on_window_size_change(self): async def on_window_size_change(self):
await self.force_refresh() self.force_refresh()
def build(self) -> Component: def build(self) -> Component:
content = Card( content = Card(

View File

@ -38,7 +38,7 @@ class BuyTicketPage(Component):
if not self.user: if not self.user:
return return
self.is_buying_enabled = False self.is_buying_enabled = False
await self.force_refresh() self.force_refresh()
try: try:
t_s = self.session[TicketingService] t_s = self.session[TicketingService]

View File

@ -1,4 +1,4 @@
from typing import Optional from typing import Optional, Callable
from rio import Column, Component, event, TextStyle, Text, Spacer, Revealer, SwitcherBar, SwitcherBarChangeEvent, ProgressCircle from rio import Column, Component, event, TextStyle, Text, Spacer, Revealer, SwitcherBar, SwitcherBarChangeEvent, ProgressCircle
@ -24,7 +24,7 @@ class CateringPage(Component):
self.all_menu_items = await self.session[CateringService].get_menu() self.all_menu_items = await self.session[CateringService].get_menu()
async def on_user_logged_in_status_changed(self) -> None: async def on_user_logged_in_status_changed(self) -> None:
await self.force_refresh() self.force_refresh()
async def on_switcher_bar_changed(self, _: SwitcherBarChangeEvent) -> None: async def on_switcher_bar_changed(self, _: SwitcherBarChangeEvent) -> None:
await self.shopping_cart_and_orders[0].switch() await self.shopping_cart_and_orders[0].switch()

View File

@ -28,7 +28,7 @@ class ContactPage(Component):
async def on_send_pressed(self) -> None: async def on_send_pressed(self) -> None:
error_msg = "" error_msg = ""
self.submit_button.is_loading = True self.submit_button.is_loading = True
await self.submit_button.force_refresh() self.submit_button.force_refresh()
now = datetime.now() now = datetime.now()
if not self.email_input.text: if not self.email_input.text:
error_msg = "E-Mail darf nicht leer sein!" error_msg = "E-Mail darf nicht leer sein!"

View File

@ -13,7 +13,7 @@ from src.ez_lan_manager.components.MainViewContentBox import MainViewContentBox
class DbErrorPage(Component): class DbErrorPage(Component):
@event.on_window_size_change @event.on_window_size_change
async def on_window_size_change(self) -> None: async def on_window_size_change(self) -> None:
await self.force_refresh() self.force_refresh()
@event.on_mount @event.on_mount
async def retry_db_connect(self) -> None: async def retry_db_connect(self) -> None:

View File

@ -20,7 +20,7 @@ class ForgotPasswordPage(Component):
async def on_submit_button_pressed(self) -> None: async def on_submit_button_pressed(self) -> None:
self.submit_button.is_loading = True self.submit_button.is_loading = True
await self.submit_button.force_refresh() self.submit_button.force_refresh()
lan_info = self.session[ConfigurationService].get_lan_info() lan_info = self.session[ConfigurationService].get_lan_info()
user_service = self.session[UserService] user_service = self.session[UserService]
mailing_service = self.session[MailingService] mailing_service = self.session[MailingService]

View File

@ -38,10 +38,10 @@ class ManageNewsPage(Component):
)) ))
self.news_posts = (await self.session[NewsService].get_news())[:8] self.news_posts = (await self.session[NewsService].get_news())[:8]
self.show_success_message = True self.show_success_message = True
await self.force_refresh() self.force_refresh()
await sleep(3) await sleep(3)
self.show_success_message = False self.show_success_message = False
await self.force_refresh() self.force_refresh()
async def on_news_post_changed(self, post: EditableNewsPost) -> None: async def on_news_post_changed(self, post: EditableNewsPost) -> None:
author = await self.session[UserService].get_user(post.author) author = await self.session[UserService].get_user(post.author)

View File

@ -35,7 +35,7 @@ class RegisterPage(Component):
async def on_submit_button_pressed(self) -> None: async def on_submit_button_pressed(self) -> None:
self.submit_button.is_loading = True self.submit_button.is_loading = True
await self.submit_button.force_refresh() self.submit_button.force_refresh()
if len(self.user_name_input.text) < 1: if len(self.user_name_input.text) < 1:
await self.animated_text.display_text(False, "Nutzername darf nicht leer sein!") await self.animated_text.display_text(False, "Nutzername darf nicht leer sein!")

View File

@ -75,7 +75,7 @@ class SeatingPlanPage(Component):
async def on_purchase_confirmed(self) -> None: async def on_purchase_confirmed(self) -> None:
self.purchase_box_loading = True self.purchase_box_loading = True
await self.force_refresh() self.force_refresh()
await sleep(0.5) await sleep(0.5)
try: try:
await self.session[SeatingService].seat_user(self.user.user_id, self.current_seat_id) await self.session[SeatingService].seat_user(self.user.user_id, self.current_seat_id)

View File

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