prerelease/0.6.0 #1
@@ -4,7 +4,7 @@ from bson import ObjectId
|
|||||||
from rio import Component, Rectangle, Column, Text, Row, PointerEventListener, TextInput
|
from rio import Component, Rectangle, Column, Text, Row, PointerEventListener, TextInput
|
||||||
from rio.event import on_populate
|
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.components import ElmButton
|
||||||
from elm.services import UserService
|
from elm.services import UserService
|
||||||
|
|
||||||
@@ -18,19 +18,25 @@ class AccountInfoBox(Component):
|
|||||||
password_change_in_progress: bool = False
|
password_change_in_progress: bool = False
|
||||||
ticket: Optional[Ticket] = None
|
ticket: Optional[Ticket] = None
|
||||||
seat: Optional[Seat] = None
|
seat: Optional[Seat] = None
|
||||||
|
fixed_user: Optional[User] = None
|
||||||
|
|
||||||
@on_populate
|
@on_populate
|
||||||
async def on_populate(self) -> None:
|
async def on_populate(self) -> None:
|
||||||
try:
|
if self.fixed_user is None:
|
||||||
user = await self.session[UserService].get_user(self.session[UserSession].user_name)
|
try:
|
||||||
if user:
|
user = await self.session[UserService].get_user(self.session[UserSession].user_name)
|
||||||
self.mail = user.user_mail
|
if user:
|
||||||
self.ticket = await Ticket.find_one({"owner.$id": user.id})
|
self.mail = user.user_mail
|
||||||
self.seat = await Seat.find_one({"user.$id": ObjectId(user.id)})
|
self.ticket = await Ticket.find_one({"owner.$id": user.id})
|
||||||
else:
|
self.seat = await Seat.find_one({"user.$id": ObjectId(user.id)})
|
||||||
|
else:
|
||||||
|
self.session.navigate_to("./login")
|
||||||
|
except KeyError:
|
||||||
self.session.navigate_to("./login")
|
self.session.navigate_to("./login")
|
||||||
except KeyError:
|
else:
|
||||||
self.session.navigate_to("./login")
|
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:
|
async def set_new_password(self) -> None:
|
||||||
self.password_change_in_progress = True
|
self.password_change_in_progress = True
|
||||||
@@ -86,7 +92,7 @@ class AccountInfoBox(Component):
|
|||||||
stroke_color=self.session.theme.box_border_color,
|
stroke_color=self.session.theme.box_border_color,
|
||||||
),
|
),
|
||||||
Column(
|
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),
|
TextInput(text=self.mail, label="E-Mail Adresse", is_sensitive=False),
|
||||||
row_col(
|
row_col(
|
||||||
PointerEventListener(
|
PointerEventListener(
|
||||||
|
|||||||
@@ -87,15 +87,33 @@ class UserNavigation(Component):
|
|||||||
async def on_populate(self) -> None:
|
async def on_populate(self) -> None:
|
||||||
self.session.create_task(self.update_balance())
|
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:
|
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(
|
return Rectangle(
|
||||||
content=Column(
|
content=Column(*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),
|
|
||||||
# UserNavigationButton("Mein Clan", "/my-clans", self.close_navigation), ToDo: Implement
|
|
||||||
UserNavigationButton("Ausloggen", "/logout", self.close_navigation)
|
|
||||||
),
|
|
||||||
min_width=3.5,
|
min_width=3.5,
|
||||||
min_height=3.5,
|
min_height=3.5,
|
||||||
fill=self.session.theme.background_color
|
fill=self.session.theme.background_color
|
||||||
|
|||||||
@@ -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
|
||||||
|
)
|
||||||
@@ -69,6 +69,8 @@ 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
|
||||||
|
|||||||
Reference in New Issue
Block a user