6 Commits

Author SHA1 Message Date
David Rodenkirchen 041ddaa334 Add participants page 2026-05-30 09:43:50 +00:00
David Rodenkirchen bbcf18d790 bump version 2026-05-30 09:43:50 +00:00
root 87eb94045c Add Python 3.11 compatibility 2026-05-29 23:17:28 +02:00
David Rodenkirchen 36418470a6 make login more sturdy 2026-05-28 13:16:00 +02:00
David Rodenkirchen 11724ad0d9 improve error handling 2026-05-28 13:10:01 +02:00
David Rodenkirchen edeefe072d Cleanup requirements 2026-05-28 13:08:48 +02:00
10 changed files with 87 additions and 8 deletions
+1 -1
View File
@@ -1 +1 @@
0.6.0-prerelease 0.6.1
BIN
View File
Binary file not shown.
+1
View File
@@ -94,6 +94,7 @@ class NavigationBar(Component):
NavigationButton("material/house", "Startseite", "/", extension_state_changed=self.on_extension_pressed), NavigationButton("material/house", "Startseite", "/", extension_state_changed=self.on_extension_pressed),
NavigationButton("material/local_activity", "Tickets", "/tickets", extension_state_changed=self.on_extension_pressed), NavigationButton("material/local_activity", "Tickets", "/tickets", extension_state_changed=self.on_extension_pressed),
NavigationButton("material/chair_alt", "Sitzplan", "/seating", extension_state_changed=self.on_extension_pressed), NavigationButton("material/chair_alt", "Sitzplan", "/seating", extension_state_changed=self.on_extension_pressed),
NavigationButton("material/group", "Teilnehmer", "/participants", extension_state_changed=self.on_extension_pressed),
NavigationButton("material/local_dining", "Catering", "/catering", extension_state_changed=self.on_extension_pressed), NavigationButton("material/local_dining", "Catering", "/catering", extension_state_changed=self.on_extension_pressed),
NavigationButton("material/trophy", "Turniere", "/tournaments", extension_state_changed=self.on_extension_pressed), NavigationButton("material/trophy", "Turniere", "/tournaments", extension_state_changed=self.on_extension_pressed),
margin_bottom=6 margin_bottom=6
+3 -2
View File
@@ -20,8 +20,9 @@ class SeatPixel(Component):
self.associated_user = await self.seat.user.fetch() self.associated_user = await self.seat.user.fetch()
async def on_press(self, _: PointerEvent) -> None: async def on_press(self, _: PointerEvent) -> None:
self.session.navigate_to(f"./seat-info?seat_id={self.seat_id.replace("\n", "")}") seat_id = self.seat_id.replace("\n", "")
self.session.navigate_to(f"./seat-info?seat_id={seat_id}")
def determine_color(self) -> Color: def determine_color(self) -> Color:
if self.seat is not None: if self.seat is not None:
try: try:
+1 -1
View File
@@ -1,6 +1,6 @@
from __future__ import annotations from __future__ import annotations
from _sha2 import sha256 from hashlib import sha256
from random import choices from random import choices
from typing import Any, Optional from typing import Any, Optional
+3
View File
@@ -32,6 +32,9 @@ class LoginPage(Component):
self.login_in_progress = True self.login_in_progress = True
user_name = copy(self.user_name) # Prevents race condition name swap user_name = copy(self.user_name) # Prevents race condition name swap
is_valid = await self.session[UserService].is_login_valid(user_name, self.password) is_valid = await self.session[UserService].is_login_valid(user_name, self.password)
if not is_valid: # Migrated users
user_name = user_name.lower().capitalize()
is_valid = await self.session[UserService].is_login_valid(user_name, self.password)
if is_valid: if is_valid:
user: User = await self.session[UserService].get_user(user_name) user: User = await self.session[UserService].get_user(user_name)
self.error_on_last_attempt = False self.error_on_last_attempt = False
+1 -1
View File
@@ -26,7 +26,7 @@ class TransactionRow(Component):
return Rectangle( return Rectangle(
content=Row( content=Row(
Text( Text(
f"{self.transaction_time.strftime("%d.%m.%y")} /", f"{self.transaction_time.strftime('%d.%m.%y')} /",
justify="left", justify="left",
font_size=0.8, font_size=0.8,
margin_left=0.5, margin_left=0.5,
+66
View File
@@ -0,0 +1,66 @@
from __future__ import annotations
from copy import copy
from typing import Any, Optional
from uuid import uuid4
from rio import Component, Column, Row, Text, Spacer, page, Color, Rectangle, TextInput, GuardEvent
from rio.event import on_populate
from elm.types import UserSession, User, Ticket, Seat
from elm.services import UserService, LocalData, LocalDataService, ConfigurationService
from elm.components import ElmButton
@page(name="Participants", url_segment="participants")
class ParticipantsPage(Component):
participants: list[tuple[User, Seat]] = []
@on_populate
async def on_populate(self) -> None:
seats = await Seat.find_many(
Seat.user != None,
fetch_links=True
).to_list()
self.participants = [(seat.user, seat) for seat in seats]
def build(self) -> Component:
return Row(
Rectangle(
content=Column(
Rectangle(
content=Rectangle(
content=Text("Teilnehmer", 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(
Row(
Text("Nutzer", grow_x=True, font_weight="bold"),
Text("Sitzplatz", font_weight="bold"),
margin=0.5
),
*[
Rectangle(
content=Row(
Text(user.user_name, grow_x=True, font_size=0.8),
Text(seat.seat_id, font_size=0.8),
margin=0.5
),
hover_fill=self.session.theme.secondary_color,
transition_time=0.2
) for user, seat in self.participants],
margin=1
),
Spacer()
),
fill=self.session.theme.box_color,
stroke_width=0.1,
stroke_color=self.session.theme.box_border_color
),
margin=1,
grow_x=True
)
+11
View File
@@ -1,8 +1,10 @@
import logging import logging
import sys
from beanie import init_beanie from beanie import init_beanie
from pymongo import AsyncMongoClient from pymongo import AsyncMongoClient
from pymongo.asynchronous.collection import AsyncCollection from pymongo.asynchronous.collection import AsyncCollection
from pymongo.errors import ServerSelectionTimeoutError, OperationFailure
from elm.types import User, Transaction, Ticket, Seat, CateringTypes from elm.types import User, Transaction, Ticket, Seat, CateringTypes
from elm.types.ConfigurationTypes import DatabaseConfiguration from elm.types.ConfigurationTypes import DatabaseConfiguration
@@ -30,6 +32,15 @@ class DatabaseService:
if self._client is None: if self._client is None:
self._client = AsyncMongoClient(mongo_uri) self._client = AsyncMongoClient(mongo_uri)
try:
await self._client.admin.command("ping")
except ServerSelectionTimeoutError:
print("Could not connect to mongodb")
sys.exit(1)
except OperationFailure:
print("Authentication with mongodb failed")
sys.exit(1)
self._database = self._client[ self._database = self._client[
self._db_config.database_name self._db_config.database_name
] ]
-3
View File
@@ -1,4 +1,3 @@
from asyncio import sleep
from hashlib import sha256 from hashlib import sha256
from typing import Optional from typing import Optional
from string import ascii_letters, digits from string import ascii_letters, digits
@@ -69,8 +68,6 @@ class UserService:
async def is_login_valid(self, user_name: str, password_clear_text: str) -> bool: async def is_login_valid(self, user_name: str, password_clear_text: str) -> bool:
user = await self.get_user(user_name) 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() user_password_hash = sha256(password_clear_text.encode(encoding="utf-8")).hexdigest()
if not user: if not user:
return False return False