prerelease/0.6.0 #1

Merged
Typhus merged 29 commits from prerelease/0.6.0 into main 2026-05-27 23:17:52 +00:00
4 changed files with 253 additions and 18 deletions
Showing only changes of commit ecc3fb35c3 - Show all commits
+17 -11
View File
@@ -4,7 +4,7 @@ from bson import ObjectId
from rio import Component, Rectangle, Column, Text, Row, PointerEventListener, TextInput
from rio.event import on_populate
from elm.types import UserSession, Ticket, Seat
from elm.types import UserSession, Ticket, Seat, User
from elm.components import ElmButton
from elm.services import UserService
@@ -18,19 +18,25 @@ class AccountInfoBox(Component):
password_change_in_progress: bool = False
ticket: Optional[Ticket] = None
seat: Optional[Seat] = None
fixed_user: Optional[User] = None
@on_populate
async def on_populate(self) -> None:
try:
user = await self.session[UserService].get_user(self.session[UserSession].user_name)
if user:
self.mail = user.user_mail
self.ticket = await Ticket.find_one({"owner.$id": user.id})
self.seat = await Seat.find_one({"user.$id": ObjectId(user.id)})
else:
if self.fixed_user is None:
try:
user = await self.session[UserService].get_user(self.session[UserSession].user_name)
if user:
self.mail = user.user_mail
self.ticket = await Ticket.find_one({"owner.$id": user.id})
self.seat = await Seat.find_one({"user.$id": ObjectId(user.id)})
else:
self.session.navigate_to("./login")
except KeyError:
self.session.navigate_to("./login")
except KeyError:
self.session.navigate_to("./login")
else:
self.mail = self.fixed_user.user_mail
self.ticket = await Ticket.find_one({"owner.$id": self.fixed_user.id})
self.seat = await Seat.find_one({"user.$id": ObjectId(self.fixed_user.id)})
async def set_new_password(self) -> None:
self.password_change_in_progress = True
@@ -86,7 +92,7 @@ class AccountInfoBox(Component):
stroke_color=self.session.theme.box_border_color,
),
Column(
TextInput(text=self.session[UserSession].user_name, label="Nutzername", is_sensitive=False),
TextInput(text=self.fixed_user.user_name if self.fixed_user is not None else self.session[UserSession].user_name, label="Nutzername", is_sensitive=False),
TextInput(text=self.mail, label="E-Mail Adresse", is_sensitive=False),
row_col(
PointerEventListener(
+25 -7
View File
@@ -87,15 +87,33 @@ class UserNavigation(Component):
async def on_populate(self) -> None:
self.session.create_task(self.update_balance())
def show_admin_navigation(self) -> bool:
try:
user_session = self.session[UserSession]
except KeyError:
return False
return user_session.is_team_member
def build(self) -> Component:
base_nav = [
UserNavigationButton(f"Guthaben: {self.session[AccountingService].make_euro_string_from_decimal(self.balance)}", "/balance", self.close_navigation),
UserNavigationButton("Meine Bestellungen", "/my-orders", self.close_navigation),
UserNavigationButton("Mein Profil", "/my-profile", self.close_navigation)
]
if self.show_admin_navigation():
base_nav.extend([
UserNavigationButton("Admin: Benutzer", "/user-admin", self.close_navigation),
UserNavigationButton("Admin: Catering", "/catering-admin", self.close_navigation)
])
base_nav.append(
UserNavigationButton("Ausloggen", "/logout", self.close_navigation)
)
return Rectangle(
content=Column(
UserNavigationButton(f"Guthaben: {self.session[AccountingService].make_euro_string_from_decimal(self.balance)}", "/balance", self.close_navigation),
UserNavigationButton("Meine Bestellungen", "/my-orders", self.close_navigation),
UserNavigationButton("Mein Profil", "/my-profile", self.close_navigation),
# UserNavigationButton("Mein Clan", "/my-clans", self.close_navigation), ToDo: Implement
UserNavigationButton("Ausloggen", "/logout", self.close_navigation)
),
content=Column(*base_nav),
min_width=3.5,
min_height=3.5,
fill=self.session.theme.background_color
+209
View File
@@ -0,0 +1,209 @@
from __future__ import annotations
import logging
from functools import partial
from typing import Optional
from decimal import Decimal
from rio import Component, Column, Row, Text, Spacer, page, Rectangle, TextInput, GuardEvent, Button, TextInputChangeEvent, NumberInput
from rio.event import on_populate
from elm.types import UserSession, User, Transaction
from elm.services import AccountingService, MailingService
from elm.components import AccountInfoBox
logger = logging.getLogger(__name__.split(".")[-1])
def user_admin_page_guard(event: GuardEvent) -> Optional[str]:
try:
if event.session[UserSession].is_team_member:
return None
return "/"
except KeyError:
return "/"
@page(name="Benutzerverwaltung", url_segment="user-admin", guard=user_admin_page_guard)
class UserAdminPage(Component):
all_users: list[User] = list()
user_list: list[User] = list()
search_bar_text: str = ""
active_user: Optional[User] = None
transaction_value: float = 0.0
transaction_reason: str = ""
active_user_balance: str = "0.00 €"
@on_populate
async def on_populate(self) -> None:
user_list = await User.find_all().to_list()
self.all_users = sorted(user_list, key=lambda u: u.user_name)
self.user_list = sorted(user_list, key=lambda u: u.user_name)
async def on_search_bar_text_changed(self, e: TextInputChangeEvent) -> None:
self.user_list = list(filter(lambda user: (e.text.lower() in user.user_name.lower()), self.all_users))
async def on_user_clicked(self, user: User) -> None:
self.active_user = user
self.active_user_balance = self.session[AccountingService].make_euro_string_from_decimal(
await self.session[AccountingService].get_balance(user.user_name)
)
async def create_debit_transaction(self) -> None:
if not self.active_user:
return
logger.info(f"Trying to remove {self.transaction_value} to {self.active_user.user_name} ({self.transaction_reason})")
new_transaction = Transaction(
user_name=self.active_user.user_name,
value=Decimal(str(self.transaction_value)),
is_debit=True,
title=self.transaction_reason
)
try:
await new_transaction.save()
except Exception as e:
logger.error(e)
self.transaction_value = 0.0
self.transaction_reason = ""
self.active_user_balance = self.session[AccountingService].make_euro_string_from_decimal(
await self.session[AccountingService].get_balance(self.active_user.user_name)
)
async def create_credit_transaction(self) -> None:
if not self.active_user:
return
logger.info(f"Trying to add {self.transaction_value} to {self.active_user.user_name} ({self.transaction_reason})")
value = Decimal(str(self.transaction_value))
new_transaction = Transaction(
user_name=self.active_user.user_name,
value=value,
is_debit=False,
title=self.transaction_reason
)
try:
await new_transaction.save()
except Exception as e:
logger.error(e)
self.transaction_value = 0.0
self.transaction_reason = ""
total_balance = await self.session[AccountingService].get_balance(self.active_user.user_name)
self.active_user_balance = self.session[AccountingService].make_euro_string_from_decimal(total_balance)
self.session.create_task(self.session[MailingService].send_email(
subject="Dein Guthaben wurde aufgeladen!",
body=self.session[MailingService].generate_account_balance_added_mail_body(user=self.active_user, added_balance=value, total_balance=total_balance),
receiver=self.active_user.user_mail
))
def build(self) -> Component:
right_panel_contents = []
if not self.active_user:
right_panel_contents.append(Spacer())
else:
right_panel_contents.extend([
AccountInfoBox(fixed_user=self.active_user),
Rectangle(
content=Column(
Rectangle(
content=Rectangle(
content=Text(f"LAN Konto - Kontostand: {self.active_user_balance}", margin=0.5, selectable=False, overflow="wrap"),
fill=self.session.theme.header_box_background_color,
margin=0.4
),
stroke_width=0.1,
stroke_color=self.session.theme.box_border_color,
),
Column(
NumberInput(
value=self.bind().transaction_value,
label="Betrag",
suffix_text="",
decimals=2,
margin=1,
margin_bottom=0
),
TextInput(
text=self.bind().transaction_reason,
label="Beschreibung",
margin=1,
margin_bottom=0
),
Row(
Button(
content="Entfernen",
shape="rectangle",
color="danger",
margin=1,
on_press=self.create_debit_transaction
),
Button(
content="Hinzufügen",
shape="rectangle",
color="success",
margin=1,
on_press=self.create_credit_transaction
)
),
margin=1,
spacing=1
),
Spacer()
),
fill=self.session.theme.box_color,
stroke_width=0.1,
stroke_color=self.session.theme.box_border_color
),
])
return Row(
Rectangle(
content=Column(
Rectangle(
content=Rectangle(
content=Text("Nutzerliste", margin=0.5, selectable=False, overflow="wrap"),
fill=self.session.theme.header_box_background_color,
margin=0.4
),
stroke_width=0.1,
stroke_color=self.session.theme.box_border_color,
),
Column(
TextInput(label="Nutzername", text=self.bind().search_bar_text, on_change=self.on_search_bar_text_changed, margin_bottom=1),
*[Button(content=user.user_name, shape="rectangle", style="plain-text", on_press=partial(self.on_user_clicked, user)) for user in self.user_list],
margin=1
),
Spacer()
),
fill=self.session.theme.box_color,
stroke_width=0.1,
stroke_color=self.session.theme.box_border_color,
min_width=25
),
Rectangle(
content=Column(
Rectangle(
content=Rectangle(
content=Text(f"Nutzer bearbeiten{': ' if self.active_user else ''}{self.active_user.user_name if self.active_user else ''}", margin=0.5, selectable=False, overflow="wrap"),
fill=self.session.theme.header_box_background_color,
margin=0.4
),
stroke_width=0.1,
stroke_color=self.session.theme.box_border_color,
),
Column(
*right_panel_contents,
margin=1,
spacing=1
),
Spacer()
),
fill=self.session.theme.box_color,
stroke_width=0.1,
stroke_color=self.session.theme.box_border_color,
grow_x=True
),
spacing=1,
margin=1
)
+2
View File
@@ -69,6 +69,8 @@ class UserService:
async def is_login_valid(self, user_name: str, password_clear_text: str) -> bool:
user = await self.get_user(user_name)
if not user:
user = await self.get_user(user_name.lower()) # Migrated users had all lowercase names
user_password_hash = sha256(password_clear_text.encode(encoding="utf-8")).hexdigest()
if not user:
return False