Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| afe5575b34 | |||
| 9da25d4197 | |||
| cf9b39ba0a | |||
| 3655e8eb53 | |||
| 43ce42052e | |||
| 0ca06c244c |
@@ -1,22 +0,0 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>EZGG LAN Manager - Wartungsmodus</title>
|
||||
<style>
|
||||
body { text-align: center; padding: 150px; }
|
||||
h1 { font-size: 50px; }
|
||||
body { font: 20px Helvetica, sans-serif; color: #333; }
|
||||
article { display: block; text-align: left; width: 650px; margin: 0 auto; }
|
||||
a { color: #dc8100; text-decoration: none; }
|
||||
a:hover { color: #333; text-decoration: none; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<article>
|
||||
<h1>Wir sind bald wieder da!</h1>
|
||||
<div>
|
||||
<p>Wir führen zurzeit Wartungsarbeiten durch und sind in kürze wieder für euch da.</p>
|
||||
<p>— Euer EZGG LAN Team</p>
|
||||
</div>
|
||||
</article>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,12 +0,0 @@
|
||||
FROM python:3.12-bookworm
|
||||
|
||||
RUN apt-get update
|
||||
RUN apt install dumb-init
|
||||
|
||||
COPY requirements.txt .
|
||||
RUN pip install -r requirements.txt
|
||||
|
||||
EXPOSE 8000
|
||||
EXPOSE 8001
|
||||
|
||||
ENTRYPOINT ["/usr/bin/dumb-init", "--"]
|
||||
@@ -1,49 +1,13 @@
|
||||
# EZGG LAN Manager
|
||||
# EZ LAN Manager
|
||||
|
||||
## Overview
|
||||
|
||||
This repository contains the code for the EZGG LAN Manager.
|
||||
This repository contains the code for the EZ LAN Manager.
|
||||
|
||||
## Development Setup
|
||||
## How to install [prod]
|
||||
|
||||
### Prerequisites
|
||||
TBD
|
||||
|
||||
- Working Installation of MariaDB Server (version `10.6.25` or later)
|
||||
+ MySQL should work too, but there are no guarantees.
|
||||
- Python 3.9 or higher
|
||||
- PyCharm or similar IDE (optional)
|
||||
## How to install [dev]
|
||||
|
||||
### Step 1: Preparing Database
|
||||
|
||||
To prepare the database, apply the SQL file located in `sql/create_database.sql` to your database server. This is easily accomplished with the MYSQL Workbench, but it can be also done by piping the file into the mariadb-server executable.
|
||||
|
||||
After creating the database, apply all patches found in `sql/*_patch.sql` in their numeric order.
|
||||
|
||||
Optionally, you can now execute the script `create_demo_database_content.py`, found in `src/ezgg_lan_manager/helpers`. Be aware that it can be buggy sometimes, especially if you overwrite existing data.
|
||||
|
||||
### Step 2: Preparing configuration
|
||||
|
||||
Use the example configuration at `config/config.example.toml` to create a `config.toml` at the base of the repository. Most of the parameters do not matter to get the development setup done, but the database credentials need to be correct.
|
||||
|
||||
### Step 3: Install dependecies
|
||||
|
||||
Use `pip install -r requirements.txt` to install the requirements. The usage of a venv is recommended.
|
||||
|
||||
### Step 4: Running the application
|
||||
|
||||
Run the application by executing the file `EzggLanManager.py` found at `src/ezgg_lan_manager`. Check the STDOUT for information regarding the port on which the application is now served.
|
||||
|
||||
## Docker Deployment
|
||||
|
||||
To get the docker compose setup running, you need to manually complete the following steps:
|
||||
|
||||
1. Create a valid `config.toml` in the project root, so it gets copied over into the container.
|
||||
2. Create the database user:
|
||||
```sql
|
||||
CREATE USER 'ezgg_lan_manager'@'%' IDENTIFIED BY 'PASSWORD';
|
||||
GRANT ALL PRIVILEGES ON ezgg_lan_manager.* TO 'ezgg_lan_manager'@'%';
|
||||
FLUSH PRIVILEGES;
|
||||
```
|
||||
3. Make sure to **NOT** use the default passwords!
|
||||
4. Apply the `create_database.sql` when starting the MariaDB container for the first time.
|
||||
5. Apply the patches (`sql/*_patch.sql`) when starting the MariaDB container for the first time.
|
||||
TBD
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[lan]
|
||||
name="EZGG LAN"
|
||||
name="EZ LAN"
|
||||
iteration="0.5"
|
||||
date_from="2024-10-30 15:00:00"
|
||||
date_till="2024-11-01 12:00:00"
|
||||
@@ -10,7 +10,7 @@
|
||||
db_password="demo_password"
|
||||
db_host="127.0.0.1"
|
||||
db_port=3306
|
||||
db_name="ezgg_lan_manager"
|
||||
db_name="ez_lan_manager"
|
||||
|
||||
[mailing]
|
||||
smtp_server=""
|
||||
@@ -19,6 +19,12 @@
|
||||
username=""
|
||||
password=""
|
||||
|
||||
[seating]
|
||||
# SeatID -> Category
|
||||
A01 = "NORMAL"
|
||||
A02 = "NORMAL"
|
||||
C01 = "LUXUS"
|
||||
|
||||
[tickets]
|
||||
[tickets."NORMAL"]
|
||||
total_tickets=30
|
||||
@@ -34,11 +40,5 @@
|
||||
additional_info="Berechtigt zur Nutzung eines verbesserten Platzes. Dieser ist mit einer höheren Internet-Bandbreite und einem Sitzkissen ausgestattet."
|
||||
is_default=false
|
||||
|
||||
[receipt_printing]
|
||||
host="127.0.0.1"
|
||||
port="5000"
|
||||
order_print_endpoint="print_order"
|
||||
password="Alkohol1"
|
||||
|
||||
[misc]
|
||||
dev_mode_active=true # Supresses E-Mail sending
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
services:
|
||||
lan_manager:
|
||||
build: .
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
PYTHONPATH: /opt/ezgg-lan-manager
|
||||
ports:
|
||||
- "8000:8000"
|
||||
- "8001:8001"
|
||||
volumes:
|
||||
- ./:/opt/ezgg-lan-manager
|
||||
entrypoint: ["/bin/sh", "-c", "cd /opt/ezgg-lan-manager/src && python3 /opt/ezgg-lan-manager/src/EzggLanManager.py"]
|
||||
|
||||
db:
|
||||
image: mariadb:latest
|
||||
environment:
|
||||
MARIADB_ROOT_PASSWORD: Alkohol1
|
||||
MARIADB_DATABASE: ezgg_lan_manager
|
||||
MARIADB_USER: ezgg_lan_manager
|
||||
MARIADB_PASSWORD: Alkohol1
|
||||
healthcheck:
|
||||
test: ["CMD", "mariadb-admin", "ping", "-h", "localhost", "-pAlkohol1"]
|
||||
interval: 5s
|
||||
timeout: 3s
|
||||
retries: 5
|
||||
ports:
|
||||
- "127.0.0.1:3306:3306"
|
||||
volumes:
|
||||
- database:/var/lib/mysql
|
||||
- ./sql/create_database.sql:/docker-entrypoint-initdb.d/init.sql
|
||||
- ./sql:/sql
|
||||
- ./tournament_data:/opt/ezgg-lan-manager/tournament_data
|
||||
|
||||
|
||||
volumes:
|
||||
database:
|
||||
@@ -1,144 +0,0 @@
|
||||
-- Apply this patch after using create_database.sql to extend the schema to support tournaments from version 0.2.0
|
||||
-- WARNING: Executing this on a post 0.2.0 database will delete all data related to tournaments !!!
|
||||
|
||||
DROP TABLE IF EXISTS `game_titles`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8 */;
|
||||
CREATE TABLE `game_titles` (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
description TEXT NOT NULL,
|
||||
web_link VARCHAR(512) NOT NULL,
|
||||
image_name VARCHAR(255) NOT NULL,
|
||||
UNIQUE KEY uq_game_title_name (name)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
|
||||
|
||||
DROP TABLE IF EXISTS `tournaments`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8 */;
|
||||
CREATE TABLE `tournaments` (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
description TEXT NOT NULL,
|
||||
game_title_id INT NOT NULL,
|
||||
format VARCHAR(20) NOT NULL, -- SE_BO1, DE_BO3, ...
|
||||
start_time DATETIME NOT NULL,
|
||||
status VARCHAR(20) NOT NULL, -- OPEN, CLOSED, ONGOING, ...
|
||||
max_participants INT NOT NULL,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT fk_tournament_game
|
||||
FOREIGN KEY (game_title_id)
|
||||
REFERENCES game_titles(id)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
|
||||
CREATE INDEX idx_tournaments_game_title
|
||||
ON tournaments(game_title_id);
|
||||
|
||||
DROP TABLE IF EXISTS `tournament_participants`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8 */;
|
||||
CREATE TABLE `tournament_participants` (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
tournament_id INT NOT NULL,
|
||||
user_id INT NOT NULL,
|
||||
participant_type VARCHAR(10) NOT NULL DEFAULT 'PLAYER',
|
||||
seed INT NULL,
|
||||
joined_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
UNIQUE KEY uq_tournament_user (tournament_id, user_id),
|
||||
|
||||
CONSTRAINT fk_tp_tournament
|
||||
FOREIGN KEY (tournament_id)
|
||||
REFERENCES tournaments(id)
|
||||
ON DELETE CASCADE,
|
||||
|
||||
CONSTRAINT fk_tp_user
|
||||
FOREIGN KEY (user_id)
|
||||
REFERENCES users(user_id)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
|
||||
CREATE INDEX idx_tp_tournament
|
||||
ON tournament_participants(tournament_id);
|
||||
CREATE INDEX idx_tp_user
|
||||
ON tournament_participants(user_id);
|
||||
|
||||
DROP TABLE IF EXISTS `tournament_rounds`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8 */;
|
||||
CREATE TABLE `tournament_rounds` (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
tournament_id INT NOT NULL,
|
||||
bracket VARCHAR(10) NOT NULL, -- UPPER, LOWER, FINAL
|
||||
round_index INT NOT NULL,
|
||||
|
||||
UNIQUE KEY uq_round (tournament_id, bracket, round_index),
|
||||
|
||||
CONSTRAINT fk_round_tournament
|
||||
FOREIGN KEY (tournament_id)
|
||||
REFERENCES tournaments(id)
|
||||
ON DELETE CASCADE
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
|
||||
CREATE INDEX idx_rounds_tournament
|
||||
ON tournament_rounds(tournament_id);
|
||||
|
||||
DROP TABLE IF EXISTS `matches`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8 */;
|
||||
CREATE TABLE `matches` (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
tournament_id INT NOT NULL,
|
||||
round_id INT NOT NULL,
|
||||
match_index INT NOT NULL,
|
||||
status VARCHAR(15) NOT NULL, -- WAITING, PENDING, COMPLETED, ...
|
||||
best_of INT NOT NULL, -- 1, 3, 5
|
||||
scheduled_time DATETIME NULL,
|
||||
completed_at DATETIME NULL,
|
||||
|
||||
UNIQUE KEY uq_match (round_id, match_index),
|
||||
|
||||
CONSTRAINT fk_match_tournament
|
||||
FOREIGN KEY (tournament_id)
|
||||
REFERENCES tournaments(id)
|
||||
ON DELETE CASCADE,
|
||||
|
||||
CONSTRAINT fk_match_round
|
||||
FOREIGN KEY (round_id)
|
||||
REFERENCES tournament_rounds(id)
|
||||
ON DELETE CASCADE
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
|
||||
CREATE INDEX idx_matches_tournament
|
||||
ON matches(tournament_id);
|
||||
|
||||
CREATE INDEX idx_matches_round
|
||||
ON matches(round_id);
|
||||
|
||||
DROP TABLE IF EXISTS `match_participants`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8 */;
|
||||
CREATE TABLE `match_participants` (
|
||||
match_id INT NOT NULL,
|
||||
participant_id INT NOT NULL,
|
||||
score INT NULL,
|
||||
is_winner TINYINT(1) NULL,
|
||||
|
||||
PRIMARY KEY (match_id, participant_id),
|
||||
|
||||
CONSTRAINT fk_mp_match
|
||||
FOREIGN KEY (match_id)
|
||||
REFERENCES matches(id)
|
||||
ON DELETE CASCADE,
|
||||
|
||||
CONSTRAINT fk_mp_participant
|
||||
FOREIGN KEY (participant_id)
|
||||
REFERENCES tournament_participants(id)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
@@ -1,63 +0,0 @@
|
||||
-- =====================================================
|
||||
-- Teams
|
||||
-- =====================================================
|
||||
|
||||
DROP TABLE IF EXISTS `team_members`;
|
||||
DROP TABLE IF EXISTS `teams`;
|
||||
|
||||
|
||||
-- -----------------------------------------------------
|
||||
-- Teams table
|
||||
-- -----------------------------------------------------
|
||||
CREATE TABLE `teams` (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
name VARCHAR(100) NOT NULL,
|
||||
abbreviation VARCHAR(10) NOT NULL,
|
||||
join_password VARCHAR(255) NOT NULL,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
UNIQUE KEY uq_team_name (name),
|
||||
UNIQUE KEY uq_team_abbr (abbreviation)
|
||||
|
||||
) ENGINE=InnoDB
|
||||
DEFAULT CHARSET=utf8mb4
|
||||
COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
|
||||
-- -----------------------------------------------------
|
||||
-- Team Members (Junction Table)
|
||||
-- -----------------------------------------------------
|
||||
CREATE TABLE `team_members` (
|
||||
team_id INT NOT NULL,
|
||||
user_id INT NOT NULL,
|
||||
|
||||
status ENUM('MEMBER','OFFICER','LEADER')
|
||||
NOT NULL DEFAULT 'MEMBER',
|
||||
|
||||
joined_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
PRIMARY KEY (team_id, user_id),
|
||||
|
||||
CONSTRAINT fk_tm_team
|
||||
FOREIGN KEY (team_id)
|
||||
REFERENCES teams(id)
|
||||
ON DELETE CASCADE,
|
||||
|
||||
CONSTRAINT fk_tm_user
|
||||
FOREIGN KEY (user_id)
|
||||
REFERENCES users(user_id)
|
||||
ON DELETE CASCADE
|
||||
|
||||
) ENGINE=InnoDB
|
||||
DEFAULT CHARSET=utf8mb4
|
||||
COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
|
||||
-- -----------------------------------------------------
|
||||
-- Indexes
|
||||
-- -----------------------------------------------------
|
||||
CREATE INDEX idx_tm_user
|
||||
ON team_members(user_id);
|
||||
|
||||
CREATE INDEX idx_tm_team_status
|
||||
ON team_members(team_id, status);
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -1,65 +0,0 @@
|
||||
INSERT INTO `catering_menu_items` VALUES
|
||||
(1,'Schnitzel Wiener Art','mit Pommes Frites und Salat',20.50,'MAIN_COURSE',1),
|
||||
(2,'Jäger Schnitzel mit Champignonrahm Sauce','mit Spätzlen und Salat',22.50,'MAIN_COURSE',1),
|
||||
(3,'Rumpsteak 250g','mit Pommes Frites und Salat',35.50,'MAIN_COURSE',1),
|
||||
(4,'Rindergeschnetzeltes','mit Spätzlen und Salat',22.50,'MAIN_COURSE',1),
|
||||
(5,'Galloway Burger','mit Amazing Fries',20.00,'MAIN_COURSE',1),
|
||||
(6,'Käsespätzle mit Röstzwiebel','mit Salat',20.00,'MAIN_COURSE',1),
|
||||
(11,'Käse Schinken Wrap','',5.00,'SNACK',1),
|
||||
(12,'Puten Paprika Wrap','',7.00,'SNACK',1),
|
||||
(13,'Tomate Mozzarella Wrap','',6.00,'SNACK',1),
|
||||
(14,'Portion Pommes','',4.00,'SNACK',1),
|
||||
(15,'Rinds-Currywurst','',4.50,'SNACK',1),
|
||||
(16,'Rinds-Currywurst mit Pommes','',6.50,'SNACK',1),
|
||||
(17,'Nudelsalat','',4.50,'SNACK',1),
|
||||
(18,'Nudelsalat mit Bockwurst','',6.00,'SNACK',1),
|
||||
(19,'Kartoffelsalat','',4.50,'SNACK',1),
|
||||
(20,'Kartoffelsalat mit Bockwurst','',6.00,'SNACK',1),
|
||||
(21,'Sandwichtoast - Schinken','',1.80,'SNACK',1),
|
||||
(22,'Sandwichtoast - Käse','',1.80,'SNACK',1),
|
||||
(23,'Sandwichtoast - Schinken/Käse','',2.10,'SNACK',1),
|
||||
(24,'Sandwichtoast - Salami','',1.80,'SNACK',1),
|
||||
(25,'Sandwichtoast - Salami/Käse','',2.10,'SNACK',1),
|
||||
(26,'Chips - Western Style','',1.50,'SNACK',1),
|
||||
(27,'Nachos - Salted','',1.50,'SNACK',1),
|
||||
(28,'Panna Cotta mit Erdbeersauce','',7.00,'DESSERT',1),
|
||||
(29,'Panna Cotta mit Blaubeersauce','',7.00,'DESSERT',1),
|
||||
(30,'Mousse au Chocolat','',7.00,'DESSERT',1),
|
||||
(31,'Fruit Loops','',1.50,'BREAKFAST',1),
|
||||
(32,'Smacks','',1.50,'BREAKFAST',1),
|
||||
(33,'Knuspermüsli','Schoko',2.00,'BREAKFAST',1),
|
||||
(34,'Cini Minis','',1.50,'BREAKFAST',1),
|
||||
(35,'Brötchen - Schinken','mit Margarine',1.20,'BREAKFAST',1),
|
||||
(36,'Brötchen - Käse','mit Margarine',1.20,'BREAKFAST',1),
|
||||
(37,'Brötchen - Schinken/Käse','mit Margarine',1.40,'BREAKFAST',1),
|
||||
(38,'Brötchen - Salami','mit Margarine',1.20,'BREAKFAST',1),
|
||||
(39,'Brötchen - Salami/Käse','mit Margarine',1.40,'BREAKFAST',1),
|
||||
(40,'Brötchen - Nutella','mit Margarine',1.20,'BREAKFAST',1),
|
||||
(41,'Wasser - Still','1L Flasche',2.00,'BEVERAGE_NON_ALCOHOLIC',1),
|
||||
(42,'Wasser - Medium','1L Flasche',2.00,'BEVERAGE_NON_ALCOHOLIC',1),
|
||||
(43,'Wasser - Spritzig','1L Flasche',2.00,'BEVERAGE_NON_ALCOHOLIC',1),
|
||||
(44,'Coca-Cola','1L Flasche',2.00,'BEVERAGE_NON_ALCOHOLIC',1),
|
||||
(45,'Coca-Cola Zero','1L Flasche',2.00,'BEVERAGE_NON_ALCOHOLIC',1),
|
||||
(46,'Fanta','1L Flasche',2.00,'BEVERAGE_NON_ALCOHOLIC',1),
|
||||
(47,'Sprite','1L Flasche',2.00,'BEVERAGE_NON_ALCOHOLIC',1),
|
||||
(48,'Spezi','von Paulaner, 0,5L Flasche',1.50,'BEVERAGE_NON_ALCOHOLIC',1),
|
||||
(49,'Red Bull','',2.00,'BEVERAGE_NON_ALCOHOLIC',1),
|
||||
(50,'Energy','Hausmarke',1.50,'BEVERAGE_NON_ALCOHOLIC',1),
|
||||
(51,'Pils','0,33L Flasche',1.90,'BEVERAGE_ALCOHOLIC',1),
|
||||
(52,'Radler','0,33L Flasche',1.90,'BEVERAGE_ALCOHOLIC',1),
|
||||
(53,'Diesel','0,33L Flasche',1.90,'BEVERAGE_ALCOHOLIC',1),
|
||||
(54,'Apfelwein Pur','0,33L Flasche',1.90,'BEVERAGE_ALCOHOLIC',1),
|
||||
(55,'Apfelwein Sauer','0,33L Flasche',1.90,'BEVERAGE_ALCOHOLIC',1),
|
||||
(56,'Apfelwein Cola','0,33L Flasche',1.90,'BEVERAGE_ALCOHOLIC',1),
|
||||
(57,'Vodka Energy','',4.00,'BEVERAGE_COCKTAIL',1),
|
||||
(58,'Vodka O-Saft','',4.00,'BEVERAGE_COCKTAIL',1),
|
||||
(59,'Whiskey Cola','mit Bourbon',4.00,'BEVERAGE_COCKTAIL',1),
|
||||
(60,'Jägermeister Energy','',4.00,'BEVERAGE_COCKTAIL',1),
|
||||
(61,'Sex on the Beach','',5.50,'BEVERAGE_COCKTAIL',1),
|
||||
(62,'Long Island Ice Tea','',5.50,'BEVERAGE_COCKTAIL',1),
|
||||
(63,'Caipirinha','',5.50,'BEVERAGE_COCKTAIL',1),
|
||||
(64,'Jägermeister','',2.00,'BEVERAGE_SHOT',1),
|
||||
(65,'Tequila','',2.00,'BEVERAGE_SHOT',1),
|
||||
(66,'Pfeffi','Pfefferminz-Schnaps',1.50,'BEVERAGE_SHOT',1),
|
||||
(67,'Zigaretten','Elixyr',8.00,'NON_FOOD',1),
|
||||
(68,'Mentholfilter','passend für Elixyr',1.20,'NON_FOOD',1);
|
||||
@@ -1,8 +1,8 @@
|
||||
CREATE DATABASE IF NOT EXISTS `ezgg_lan_manager` /*!40100 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci */;
|
||||
USE `ezgg_lan_manager`;
|
||||
CREATE DATABASE IF NOT EXISTS `ez_lan_manager` /*!40100 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci */;
|
||||
USE `ez_lan_manager`;
|
||||
-- MySQL dump 10.13 Distrib 5.7.24, for Linux (x86_64)
|
||||
--
|
||||
-- Host: 127.0.0.1 Database: ezgg_lan_manager
|
||||
-- Host: 127.0.0.1 Database: ez_lan_manager
|
||||
-- ------------------------------------------------------
|
||||
-- Server version 5.5.5-10.11.8-MariaDB-0ubuntu0.24.04.1
|
||||
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
INSERT INTO `seats` (`seat_id`, `is_blocked`, `seat_category`) VALUES
|
||||
('A01', '0', 'NORMAL'),
|
||||
('A02', '0', 'NORMAL'),
|
||||
('A03', '0', 'NORMAL'),
|
||||
('A04', '0', 'NORMAL'),
|
||||
('A05', '0', 'NORMAL'),
|
||||
('A06', '0', 'NORMAL'),
|
||||
('A10', '0', 'NORMAL'),
|
||||
('A11', '0', 'NORMAL'),
|
||||
('A12', '0', 'NORMAL'),
|
||||
('A13', '0', 'NORMAL'),
|
||||
('A14', '0', 'NORMAL'),
|
||||
('A15', '0', 'NORMAL'),
|
||||
|
||||
('B01', '0', 'NORMAL'),
|
||||
('B02', '0', 'NORMAL'),
|
||||
('B03', '0', 'NORMAL'),
|
||||
('B04', '0', 'NORMAL'),
|
||||
('B05', '0', 'NORMAL'),
|
||||
('B10', '0', 'NORMAL'),
|
||||
('B11', '0', 'NORMAL'),
|
||||
('B12', '0', 'NORMAL'),
|
||||
('B13', '0', 'NORMAL'),
|
||||
('B14', '0', 'NORMAL'),
|
||||
|
||||
('C01', '0', 'NORMAL'),
|
||||
('C02', '0', 'NORMAL'),
|
||||
('C03', '0', 'NORMAL'),
|
||||
('C04', '0', 'NORMAL'),
|
||||
('C05', '0', 'NORMAL'),
|
||||
('C10', '0', 'NORMAL'),
|
||||
('C11', '0', 'NORMAL'),
|
||||
('C12', '0', 'NORMAL'),
|
||||
('C13', '0', 'NORMAL'),
|
||||
('C14', '0', 'NORMAL'),
|
||||
|
||||
('D01', '0', 'NORMAL'),
|
||||
('D02', '0', 'NORMAL'),
|
||||
('D03', '0', 'NORMAL'),
|
||||
('D04', '0', 'NORMAL'),
|
||||
('D05', '0', 'NORMAL'),
|
||||
('D06', '0', 'NORMAL'),
|
||||
('D10', '0', 'NORMAL'),
|
||||
('D11', '0', 'NORMAL'),
|
||||
('D12', '0', 'NORMAL'),
|
||||
('D13', '0', 'NORMAL'),
|
||||
('D14', '0', 'NORMAL'),
|
||||
('D15', '0', 'NORMAL'),
|
||||
|
||||
('E01', '0', 'NORMAL'),
|
||||
('E02', '0', 'NORMAL'),
|
||||
('E03', '0', 'NORMAL'),
|
||||
('E04', '0', 'NORMAL'),
|
||||
('E05', '0', 'NORMAL'),
|
||||
('E06', '0', 'NORMAL'),
|
||||
('E10', '0', 'NORMAL'),
|
||||
('E11', '0', 'NORMAL'),
|
||||
('E12', '0', 'NORMAL'),
|
||||
('E13', '0', 'NORMAL'),
|
||||
('E14', '0', 'NORMAL'),
|
||||
('E15', '0', 'NORMAL');
|
||||
@@ -1,5 +1,5 @@
|
||||
import logging
|
||||
from uuid import uuid4
|
||||
from asyncio import get_event_loop
|
||||
|
||||
import sys
|
||||
|
||||
@@ -8,12 +8,13 @@ from pathlib import Path
|
||||
from rio import App, Theme, Color, Font, ComponentPage, Session
|
||||
from from_root import from_root
|
||||
|
||||
from src.ezgg_lan_manager import pages, init_services, LocalDataService, RefreshService
|
||||
from src.ezgg_lan_manager.helpers.LoggedInGuard import logged_in_guard, not_logged_in_guard, team_guard
|
||||
from src.ezgg_lan_manager.services.LocalDataService import LocalData
|
||||
from src.ezgg_lan_manager.types.UserSession import UserSession
|
||||
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.services.DatabaseService import NoDatabaseConnectionError
|
||||
from src.ez_lan_manager.services.LocalDataService import LocalData
|
||||
from src.ez_lan_manager.types.SessionStorage import SessionStorage
|
||||
|
||||
logger = logging.getLogger("EzggLanManager")
|
||||
logger = logging.getLogger("EzLanManager")
|
||||
|
||||
if __name__ == "__main__":
|
||||
theme = Theme.from_colors(
|
||||
@@ -27,22 +28,16 @@ if __name__ == "__main__":
|
||||
corner_radius_small=0,
|
||||
corner_radius_medium=0,
|
||||
corner_radius_large=0,
|
||||
font=Font(from_root("src/ezgg_lan_manager/assets/fonts/joystix.otf"))
|
||||
font=Font(from_root("src/ez_lan_manager/assets/fonts/joystix.otf"))
|
||||
)
|
||||
default_attachments: list = [LocalData(stored_session_token=None)]
|
||||
default_attachments = [LocalData()]
|
||||
default_attachments.extend(init_services())
|
||||
|
||||
lan_info = default_attachments[3].get_lan_info()
|
||||
|
||||
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)
|
||||
session.attach(RefreshService())
|
||||
# if session[LocalData].stored_session_token:
|
||||
# user_session = session[LocalDataService].verify_token(session[LocalData].stored_session_token)
|
||||
# if user_session is not None:
|
||||
# session.attach(user_session)
|
||||
session.attach(SessionStorage())
|
||||
|
||||
async def on_app_start(a: App) -> None:
|
||||
init_result = await a.default_attachments[4].init_db_pool()
|
||||
@@ -51,7 +46,7 @@ if __name__ == "__main__":
|
||||
sys.exit(1)
|
||||
|
||||
app = App(
|
||||
name="EZGG LAN Manager",
|
||||
name="EZ LAN Manager",
|
||||
build=pages.BasePage,
|
||||
pages=[
|
||||
ComponentPage(
|
||||
@@ -67,7 +62,7 @@ if __name__ == "__main__":
|
||||
ComponentPage(
|
||||
name="Overview",
|
||||
url_segment="overview",
|
||||
build=pages.OverviewPage,
|
||||
build=lambda: pages.PlaceholderPage(placeholder_name="LAN Übersicht"),
|
||||
),
|
||||
ComponentPage(
|
||||
name="BuyTicket",
|
||||
@@ -162,41 +157,10 @@ if __name__ == "__main__":
|
||||
build=pages.ManageTournamentsPage,
|
||||
guard=team_guard
|
||||
),
|
||||
ComponentPage(
|
||||
name="AdminNavigationPage",
|
||||
url_segment="admin",
|
||||
build=pages.AdminNavigationPage,
|
||||
guard=team_guard
|
||||
),
|
||||
ComponentPage(
|
||||
name="DbErrorPage",
|
||||
url_segment="db-error",
|
||||
build=pages.DbErrorPage,
|
||||
),
|
||||
ComponentPage(
|
||||
name="TournamentDetailsPage",
|
||||
url_segment="tournament",
|
||||
build=pages.TournamentDetailsPage,
|
||||
),
|
||||
ComponentPage(
|
||||
name="TournamentTreePage",
|
||||
url_segment="tournament-tree",
|
||||
build=pages.TournamentTreePage,
|
||||
),
|
||||
ComponentPage(
|
||||
name="TournamentRulesPage",
|
||||
url_segment="tournament-rules",
|
||||
build=pages.TournamentRulesPage,
|
||||
),
|
||||
ComponentPage(
|
||||
name="Teams",
|
||||
url_segment="teams",
|
||||
build=pages.TeamsPage,
|
||||
),
|
||||
ComponentPage(
|
||||
name="ConwaysGameOfLife",
|
||||
url_segment="conway",
|
||||
build=pages.ConwayPage,
|
||||
)
|
||||
],
|
||||
theme=theme,
|
||||
@@ -204,13 +168,13 @@ if __name__ == "__main__":
|
||||
default_attachments=default_attachments,
|
||||
on_session_start=on_session_start,
|
||||
on_app_start=on_app_start,
|
||||
icon=from_root("src/ezgg_lan_manager/assets/img/favicon.png"),
|
||||
icon=from_root("src/ez_lan_manager/assets/img/favicon.png"),
|
||||
meta_tags={
|
||||
"robots": "INDEX,FOLLOW",
|
||||
"description": f"Info und Verwaltungs-Seite der LAN Party '{lan_info.name} - {lan_info.iteration}'.",
|
||||
"og:description": f"Info und Verwaltungs-Seite der LAN Party '{lan_info.name} - {lan_info.iteration}'.",
|
||||
"keywords": "Gaming, Clan, Guild, Verein, Club, Einfach, Zocken, Gesellschaft, Videospiele, "
|
||||
"Videogames, LAN, Party, EZ, EZGG, LAN, Manager",
|
||||
"Videogames, LAN, Party, EZ, LAN, Manager",
|
||||
"author": "David Rodenkirchen",
|
||||
"publisher": "EZ GG e.V.",
|
||||
"copyright": "EZ GG e.V.",
|
||||
@@ -222,14 +186,4 @@ if __name__ == "__main__":
|
||||
}
|
||||
)
|
||||
|
||||
try:
|
||||
app.run_as_web_server(
|
||||
host="0.0.0.0",
|
||||
port=8000,
|
||||
)
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
logger.info("EZGG LAN Manager was shut down.")
|
||||
sys.exit(0)
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
sys.exit(1)
|
||||
sys.exit(app.run_as_web_server())
|
||||
@@ -0,0 +1,32 @@
|
||||
import logging
|
||||
|
||||
from from_root import from_root
|
||||
|
||||
from src.ez_lan_manager.services import *
|
||||
from src.ez_lan_manager.services.AccountingService import AccountingService
|
||||
from src.ez_lan_manager.services.CateringService import CateringService
|
||||
from src.ez_lan_manager.services.ConfigurationService import ConfigurationService
|
||||
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.NewsService import NewsService
|
||||
from src.ez_lan_manager.services.SeatingService import SeatingService
|
||||
from src.ez_lan_manager.services.TicketingService import TicketingService
|
||||
from src.ez_lan_manager.services.UserService import UserService
|
||||
from src.ez_lan_manager.types import *
|
||||
|
||||
# Inits services in the correct order
|
||||
def init_services() -> tuple[AccountingService, CateringService, ConfigurationService, DatabaseService, MailingService, NewsService, SeatingService, TicketingService, UserService, LocalDataService]:
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
configuration_service = ConfigurationService(from_root("config.toml"))
|
||||
db_service = DatabaseService(configuration_service.get_database_configuration())
|
||||
user_service = UserService(db_service)
|
||||
accounting_service = AccountingService(db_service)
|
||||
news_service = NewsService(db_service)
|
||||
mailing_service = MailingService(configuration_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)
|
||||
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, local_data_service
|
||||
|
Before Width: | Height: | Size: 100 KiB After Width: | Height: | Size: 100 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
@@ -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
|
||||
@@ -4,7 +4,7 @@ from decimal import Decimal
|
||||
import rio
|
||||
from rio import Component, Row, Text, IconButton, TextStyle
|
||||
|
||||
from src.ezgg_lan_manager import AccountingService
|
||||
from src.ez_lan_manager import AccountingService
|
||||
|
||||
MAX_LEN = 24
|
||||
|
||||
@@ -3,9 +3,9 @@ from typing import Optional, Callable
|
||||
|
||||
from rio import Component, Row, Card, Column, Text, TextStyle, Spacer, PointerEventListener, Button
|
||||
|
||||
from src.ezgg_lan_manager.services.CateringService import CateringService
|
||||
from src.ezgg_lan_manager.types.CateringOrder import CateringOrder, CateringOrderStatus
|
||||
from src.ezgg_lan_manager.types.Seat import Seat
|
||||
from src.ez_lan_manager.services.CateringService import CateringService
|
||||
from src.ez_lan_manager.types.CateringOrder import CateringOrder, CateringOrderStatus
|
||||
from src.ez_lan_manager.types.Seat import Seat
|
||||
|
||||
class CateringManagementOrderDisplayStatusButton(Component):
|
||||
status: CateringOrderStatus
|
||||
@@ -1,8 +1,9 @@
|
||||
from typing import Callable
|
||||
|
||||
from rio import Component, Row, Text, TextStyle, Color, Rectangle, PointerEventListener
|
||||
from rio import Component, Row, Text, TextStyle, Color, Rectangle, CursorStyle
|
||||
from rio.components.pointer_event_listener import PointerEvent, PointerEventListener
|
||||
|
||||
from src.ezgg_lan_manager.types.CateringOrder import CateringOrderStatus, CateringOrder
|
||||
from src.ez_lan_manager.types.CateringOrder import CateringOrderStatus, CateringOrder
|
||||
|
||||
MAX_LEN = 24
|
||||
|
||||
@@ -40,7 +41,7 @@ class CateringOrderItem(Component):
|
||||
fill=self.session.theme.primary_color,
|
||||
hover_fill=self.session.theme.hud_color,
|
||||
transition_time=0.1,
|
||||
cursor="pointer"
|
||||
cursor=CursorStyle.POINTER
|
||||
),
|
||||
on_press=lambda _: self.info_modal_cb(self.order),
|
||||
)
|
||||
@@ -4,7 +4,7 @@ from typing import Callable
|
||||
import rio
|
||||
from rio import Component, Row, Text, IconButton, TextStyle, Column, Spacer, Card, Color
|
||||
|
||||
from src.ezgg_lan_manager import AccountingService
|
||||
from src.ez_lan_manager import AccountingService
|
||||
|
||||
MAX_LEN = 24
|
||||
|
||||
@@ -46,10 +46,10 @@ class CateringSelectionItem(Component):
|
||||
Text(AccountingService.make_euro_string_from_decimal(self.article_price),
|
||||
style=TextStyle(fill=self.session.theme.background_color, font_size=0.9)),
|
||||
IconButton(
|
||||
icon="material/add" if self.is_sensitive else "material/do_not_disturb_on_total_silence",
|
||||
icon="material/add",
|
||||
min_size=2,
|
||||
color=self.session.theme.success_color if self.is_sensitive else self.session.theme.danger_color,
|
||||
style="colored-text",
|
||||
color=self.session.theme.success_color,
|
||||
style="plain-text",
|
||||
on_press=lambda: self.on_add_callback(self.article_id),
|
||||
is_sensitive=self.is_sensitive
|
||||
),
|
||||
@@ -0,0 +1,93 @@
|
||||
from copy import copy, deepcopy
|
||||
from typing import Optional, Callable
|
||||
|
||||
from rio import *
|
||||
|
||||
from src.ez_lan_manager import ConfigurationService, UserService, LocalDataService
|
||||
from src.ez_lan_manager.components.DesktopNavigationButton import DesktopNavigationButton
|
||||
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.User import User
|
||||
|
||||
|
||||
class DesktopNavigation(Component):
|
||||
user: Optional[User] = None
|
||||
force_login_box_refresh: list[Callable] = []
|
||||
|
||||
@event.on_populate
|
||||
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:
|
||||
# Hack-around, maybe fix in the future
|
||||
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
|
||||
|
||||
def build(self) -> Component:
|
||||
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 = [
|
||||
DesktopNavigationButton("News", "./news"),
|
||||
Spacer(min_height=1),
|
||||
DesktopNavigationButton(f"Über {lan_info.name} {lan_info.iteration}", "./overview"),
|
||||
DesktopNavigationButton("Ticket kaufen", "./buy_ticket"),
|
||||
DesktopNavigationButton("Sitzplan", "./seating"),
|
||||
DesktopNavigationButton("Catering", "./catering"),
|
||||
DesktopNavigationButton("Teilnehmer", "./guests"),
|
||||
DesktopNavigationButton("Turniere", "./tournaments"),
|
||||
DesktopNavigationButton("FAQ", "./faq"),
|
||||
DesktopNavigationButton("Regeln & AGB", "./rules-gtc"),
|
||||
Spacer(min_height=1),
|
||||
DesktopNavigationButton("Discord", "#", open_new_tab=True), # Temporarily disabled: https://discord.gg/8gTjg34yyH
|
||||
DesktopNavigationButton("Die EZ GG e.V.", "https://ezgg-ev.de/about", open_new_tab=True),
|
||||
DesktopNavigationButton("Kontakt", "./contact"),
|
||||
DesktopNavigationButton("Impressum & DSGVO", "./imprint"),
|
||||
Spacer(min_height=1)
|
||||
]
|
||||
team_navigation = [
|
||||
Text("Verwaltung", align_x=0.5, margin_top=0.3, style=TextStyle(fill=Color.from_hex("F0EADE"), font_size=1.2)),
|
||||
Text("Vorsichtig sein!", align_x=0.5, margin_top=0.3, style=TextStyle(fill=self.session.theme.danger_color, font_size=0.6)),
|
||||
DesktopNavigationButton("News", "./manage-news", is_team_navigation=True),
|
||||
DesktopNavigationButton("Benutzer", "./manage-users", is_team_navigation=True),
|
||||
DesktopNavigationButton("Catering", "./manage-catering", is_team_navigation=True),
|
||||
DesktopNavigationButton("Turniere", "./manage-tournaments", is_team_navigation=True),
|
||||
Spacer(min_height=1),
|
||||
Revealer(
|
||||
header="Normale Navigation",
|
||||
content=Column(*user_navigation),
|
||||
header_style=TextStyle(fill=self.session.theme.primary_color, font_size=0.9)
|
||||
)
|
||||
] if self.user is not None and self.user.is_team_member else []
|
||||
|
||||
nav_to_use = copy(team_navigation) if self.user is not None and self.user.is_team_member else copy(user_navigation)
|
||||
|
||||
return Card(
|
||||
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(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),
|
||||
user_info_and_login_box,
|
||||
*nav_to_use,
|
||||
align_y=0
|
||||
),
|
||||
color=self.session.theme.neutral_color,
|
||||
min_width=15,
|
||||
grow_y=True,
|
||||
corner_radius=(0.5, 0, 0, 0),
|
||||
margin_right=0.1
|
||||
)
|
||||
@@ -1,17 +1,15 @@
|
||||
import uuid
|
||||
|
||||
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.UserService import UserService
|
||||
from src.ezgg_lan_manager.types.User import User
|
||||
from src.ezgg_lan_manager.types.UserSession import UserSession
|
||||
from src.ez_lan_manager.services.LocalDataService import LocalDataService, LocalData
|
||||
from src.ez_lan_manager.services.UserService import UserService
|
||||
from src.ez_lan_manager.types.SessionStorage import SessionStorage
|
||||
from src.ez_lan_manager.types.User import User
|
||||
|
||||
|
||||
class LoginBox(Component):
|
||||
status_change_cb: EventHandler = None
|
||||
TEXT_STYLE = TextStyle(fill=Color.from_hex("02dac5"), font_size=0.9)
|
||||
user_name_input_text: str = ""
|
||||
password_input_text: str = ""
|
||||
user_name_input_is_valid = True
|
||||
@@ -29,13 +27,11 @@ class LoginBox(Component):
|
||||
self.password_input_is_valid = True
|
||||
self.login_button_is_loading = False
|
||||
self.is_account_locked = False
|
||||
user_session = UserSession(id=uuid.uuid4(), user_id=user.user_id, is_team_member=user.is_team_member)
|
||||
self.session.attach(user_session)
|
||||
token = self.session[LocalDataService].set_session(user_session)
|
||||
await self.session[SessionStorage].set_user_id_and_team_member_flag(user.user_id, user.is_team_member)
|
||||
token = self.session[LocalDataService].set_session(self.session[SessionStorage])
|
||||
self.session[LocalData].stored_session_token = token
|
||||
self.session.attach(self.session[LocalData])
|
||||
await self.status_change_cb()
|
||||
await self.session[RefreshService].trigger_refresh()
|
||||
self.status_change_cb()
|
||||
else:
|
||||
self.user_name_input_is_valid = False
|
||||
self.password_input_is_valid = False
|
||||
@@ -61,7 +57,7 @@ class LoginBox(Component):
|
||||
is_valid=self.password_input_is_valid
|
||||
)
|
||||
login_button = Button(
|
||||
Text("LOGIN", fill=Color.from_hex("02dac5"), style=TextStyle(font_size=0.9), justify="center"),
|
||||
Text("LOGIN", style=self.TEXT_STYLE, justify="center"),
|
||||
shape="rectangle",
|
||||
style="minor",
|
||||
color="secondary",
|
||||
@@ -69,14 +65,14 @@ class LoginBox(Component):
|
||||
on_press=self._on_login_pressed
|
||||
)
|
||||
register_button = Button(
|
||||
Text("REG", fill=Color.from_hex("02dac5"), style=TextStyle(font_size=0.9), justify="center"),
|
||||
Text("REG", style=self.TEXT_STYLE, justify="center"),
|
||||
shape="rectangle",
|
||||
style="minor",
|
||||
color="secondary",
|
||||
on_press=lambda: self.session.navigate_to("./register")
|
||||
)
|
||||
forgot_password_button = Button(
|
||||
Text("LST PWD", fill=Color.from_hex("02dac5"), style=TextStyle(font_size=0.9), justify="center"),
|
||||
Text("LST PWD", style=self.TEXT_STYLE, justify="center"),
|
||||
shape="rectangle",
|
||||
style="minor",
|
||||
color="secondary",
|
||||
@@ -99,7 +95,7 @@ class LoginBox(Component):
|
||||
),
|
||||
margin_bottom=0.5
|
||||
),
|
||||
Text(text="Dieses Konto\nist gesperrt", fill=self.session.theme.danger_color, style=TextStyle(font_size=0.9 if self.is_account_locked else 0), align_x=0.5),
|
||||
Text(text="Dieses Konto\nist gesperrt", style=TextStyle(fill=self.session.theme.danger_color, font_size=0.9 if self.is_account_locked else 0), align_x=0.5),
|
||||
spacing=0.4
|
||||
),
|
||||
fill=Color.TRANSPARENT,
|
||||
@@ -107,5 +103,5 @@ class LoginBox(Component):
|
||||
min_width=12,
|
||||
align_x=0.5,
|
||||
margin_top=0.3,
|
||||
margin_bottom=1.5
|
||||
)
|
||||
margin_bottom=2
|
||||
)
|
||||
@@ -4,8 +4,8 @@ from typing import Optional
|
||||
|
||||
from rio import Component, Column, NumberInput, ThemeContextSwitcher, TextInput, Row, Button, EventHandler
|
||||
|
||||
from src.ezgg_lan_manager.types.Transaction import Transaction
|
||||
from src.ezgg_lan_manager.types.User import User
|
||||
from src.ez_lan_manager.types.Transaction import Transaction
|
||||
from src.ez_lan_manager.types.User import User
|
||||
|
||||
|
||||
class NewTransactionForm(Component):
|
||||
@@ -46,6 +46,7 @@ class NewTransactionForm(Component):
|
||||
label="Betrag",
|
||||
suffix_text="€",
|
||||
decimals=2,
|
||||
thousands_separator=".",
|
||||
margin=1,
|
||||
margin_bottom=0
|
||||
),
|
||||
@@ -21,8 +21,8 @@ class NewsPost(Component):
|
||||
grow_x=True,
|
||||
margin=2,
|
||||
margin_bottom=0,
|
||||
fill=self.session.theme.background_color,
|
||||
style=TextStyle(
|
||||
fill=self.session.theme.background_color,
|
||||
font_size=1.3
|
||||
),
|
||||
overflow="ellipsize"
|
||||
@@ -31,8 +31,8 @@ class NewsPost(Component):
|
||||
self.date,
|
||||
margin=2,
|
||||
align_x=1,
|
||||
fill=self.session.theme.background_color,
|
||||
style=TextStyle(
|
||||
fill=self.session.theme.background_color,
|
||||
font_size=0.6
|
||||
),
|
||||
overflow="wrap"
|
||||
@@ -44,8 +44,8 @@ class NewsPost(Component):
|
||||
margin=2,
|
||||
margin_top=0,
|
||||
margin_bottom=0,
|
||||
fill=self.session.theme.background_color,
|
||||
style=TextStyle(
|
||||
fill=self.session.theme.background_color,
|
||||
font_size=0.8
|
||||
),
|
||||
overflow="ellipsize"
|
||||
@@ -53,7 +53,9 @@ class NewsPost(Component):
|
||||
Text(
|
||||
self.text,
|
||||
margin=2,
|
||||
fill=self.session.theme.background_color,
|
||||
style=TextStyle(
|
||||
fill=self.session.theme.background_color
|
||||
),
|
||||
overflow="wrap"
|
||||
),
|
||||
Text(
|
||||
@@ -63,8 +65,8 @@ class NewsPost(Component):
|
||||
margin=2,
|
||||
margin_top=0,
|
||||
margin_bottom=1,
|
||||
fill=self.session.theme.background_color,
|
||||
style=TextStyle(
|
||||
fill=self.session.theme.background_color,
|
||||
font_size=0.5,
|
||||
italic=True
|
||||
),
|
||||
@@ -0,0 +1,194 @@
|
||||
from typing import Callable
|
||||
|
||||
from rio import Component, Rectangle, Grid, Column, Row, Text, TextStyle, Color
|
||||
|
||||
from src.ez_lan_manager.components.SeatingPlanPixels import SeatPixel, WallPixel, InvisiblePixel, TextPixel
|
||||
from src.ez_lan_manager.types.Seat import Seat
|
||||
|
||||
MAX_GRID_WIDTH_PIXELS = 34
|
||||
MAX_GRID_HEIGHT_PIXELS = 45
|
||||
|
||||
class SeatingPlanLegend(Component):
|
||||
def build(self) -> Component:
|
||||
return Column(
|
||||
Text("Legende", style=TextStyle(fill=self.session.theme.neutral_color), justify="center", margin=1),
|
||||
Row(
|
||||
Text("L = Luxus Platz", justify="center", style=TextStyle(fill=self.session.theme.neutral_color)),
|
||||
Text("N = Normaler Platz", justify="center", style=TextStyle(fill=self.session.theme.neutral_color)),
|
||||
),
|
||||
Row(
|
||||
Rectangle(
|
||||
content=Column(
|
||||
Text(f"Freier Platz", style=TextStyle(fill=self.session.theme.primary_color, font_size=0.7), align_x=0.5, selectable=False),
|
||||
Text(f"", style=TextStyle(fill=self.session.theme.primary_color, font_size=0.9), align_x=0.5,
|
||||
selectable=False, overflow="wrap")
|
||||
),
|
||||
min_width=1,
|
||||
min_height=1,
|
||||
fill=self.session.theme.success_color,
|
||||
grow_x=False,
|
||||
grow_y=False,
|
||||
hover_fill=self.session.theme.success_color,
|
||||
transition_time=0.4,
|
||||
ripple=True
|
||||
),
|
||||
Rectangle(
|
||||
content=Column(
|
||||
Text(f"Belegter Platz", style=TextStyle(fill=self.session.theme.primary_color, font_size=0.7), align_x=0.5, selectable=False),
|
||||
Text(f"", style=TextStyle(fill=self.session.theme.primary_color, font_size=0.9), align_x=0.5,
|
||||
selectable=False, overflow="wrap")
|
||||
),
|
||||
min_width=1,
|
||||
min_height=1,
|
||||
fill=self.session.theme.danger_color,
|
||||
grow_x=False,
|
||||
grow_y=False,
|
||||
hover_fill=self.session.theme.danger_color,
|
||||
transition_time=0.4,
|
||||
ripple=True
|
||||
),
|
||||
Rectangle(
|
||||
content=Column(
|
||||
Text(f"Eigener Platz", style=TextStyle(fill=self.session.theme.primary_color, font_size=0.7), align_x=0.5, selectable=False),
|
||||
Text(f"", style=TextStyle(fill=self.session.theme.primary_color, font_size=0.9), align_x=0.5,
|
||||
selectable=False, overflow="wrap")
|
||||
),
|
||||
min_width=1,
|
||||
min_height=1,
|
||||
fill=Color.from_hex("800080"),
|
||||
grow_x=False,
|
||||
grow_y=False,
|
||||
hover_fill=Color.from_hex("800080"),
|
||||
transition_time=0.4,
|
||||
ripple=True
|
||||
),
|
||||
margin=1,
|
||||
spacing=1
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class SeatingPlan(Component):
|
||||
seat_clicked_cb: Callable
|
||||
seating_info: list[Seat]
|
||||
|
||||
def get_seat(self, seat_id: str) -> Seat:
|
||||
return next(filter(lambda seat: seat.seat_id == seat_id, self.seating_info))
|
||||
|
||||
"""
|
||||
This seating plan is for the community center "Bottenhorn"
|
||||
"""
|
||||
def build(self) -> Component:
|
||||
grid = Grid()
|
||||
# Outlines
|
||||
for column_id in range(0, MAX_GRID_WIDTH_PIXELS):
|
||||
grid.add(InvisiblePixel(), row=0, column=column_id)
|
||||
for y in range(0, 13):
|
||||
grid.add(WallPixel(), row=y, column=0)
|
||||
for y in range(13, 19):
|
||||
grid.add(InvisiblePixel(), row=y, column=0)
|
||||
for y in range(19, MAX_GRID_HEIGHT_PIXELS):
|
||||
grid.add(WallPixel(), row=y, column=0)
|
||||
|
||||
# Block A
|
||||
block_a_margin_left = 12
|
||||
block_a_margin_top = 1
|
||||
(grid
|
||||
.add(SeatPixel("A01", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("A01")), row=block_a_margin_top, column=block_a_margin_left, width=2, height=3)
|
||||
.add(SeatPixel("A02", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("A02")), row=block_a_margin_top + 4, column=block_a_margin_left, width=2, height=3)
|
||||
.add(SeatPixel("A03", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("A03")), row=block_a_margin_top + 8, column=block_a_margin_left, width=2, height=3)
|
||||
.add(SeatPixel("A10", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("A10")), row=block_a_margin_top, column=block_a_margin_left + 3, width=2, height=3)
|
||||
.add(SeatPixel("A11", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("A11")), row=block_a_margin_top + 4, column=block_a_margin_left + 3, width=2, height=3)
|
||||
.add(SeatPixel("A12", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("A12")), row=block_a_margin_top + 8, column=block_a_margin_left + 3, width=2, height=3)
|
||||
)
|
||||
|
||||
# Block B
|
||||
block_b_margin_left = 20
|
||||
block_b_margin_top = 1
|
||||
(grid
|
||||
.add(SeatPixel("B01", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("B01")), row=block_b_margin_top, column=block_b_margin_left, width=2, height=3)
|
||||
.add(SeatPixel("B02", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("B02")), row=block_b_margin_top + 4, column=block_b_margin_left, width=2, height=3)
|
||||
.add(SeatPixel("B03", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("B03")), row=block_b_margin_top + 8, column=block_b_margin_left, width=2, height=3)
|
||||
.add(SeatPixel("B10", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("B10")), row=block_b_margin_top, column=block_b_margin_left + 3, width=2, height=3)
|
||||
.add(SeatPixel("B11", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("B11")), row=block_b_margin_top + 4, column=block_b_margin_left + 3, width=2, height=3)
|
||||
.add(SeatPixel("B12", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("B12")), row=block_b_margin_top + 8, column=block_b_margin_left + 3, width=2, height=3)
|
||||
)
|
||||
|
||||
# Block C
|
||||
block_c_margin_left = 28
|
||||
block_c_margin_top = 1
|
||||
(grid
|
||||
.add(SeatPixel("C01", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("C01")), row=block_c_margin_top, column=block_c_margin_left, width=2, height=3)
|
||||
.add(SeatPixel("C02", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("C02")), row=block_c_margin_top + 4, column=block_c_margin_left, width=2, height=3)
|
||||
.add(SeatPixel("C03", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("C03")), row=block_c_margin_top + 8, column=block_c_margin_left, width=2, height=3)
|
||||
.add(SeatPixel("C10", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("C10")), row=block_c_margin_top, column=block_c_margin_left + 3, width=2, height=3)
|
||||
.add(SeatPixel("C11", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("C11")), row=block_c_margin_top + 4, column=block_c_margin_left + 3, width=2, height=3)
|
||||
.add(SeatPixel("C12", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("C12")), row=block_c_margin_top + 8, column=block_c_margin_left + 3, width=2, height=3)
|
||||
)
|
||||
|
||||
# Block D
|
||||
block_d_margin_left = 20
|
||||
block_d_margin_top = 20
|
||||
(grid
|
||||
.add(SeatPixel("D01", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("D01")), row=block_d_margin_top, column=block_d_margin_left, width=2, height=3)
|
||||
.add(SeatPixel("D02", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("D02")), row=block_d_margin_top + 4, column=block_d_margin_left, width=2, height=3)
|
||||
.add(SeatPixel("D03", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("D03")), row=block_d_margin_top + 8, column=block_d_margin_left, width=2, height=3)
|
||||
.add(SeatPixel("D10", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("D10")), row=block_d_margin_top, column=block_d_margin_left + 3, width=2, height=3)
|
||||
.add(SeatPixel("D11", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("D11")), row=block_d_margin_top + 4, column=block_d_margin_left + 3, width=2, height=3)
|
||||
.add(SeatPixel("D12", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("D12")), row=block_d_margin_top + 8, column=block_d_margin_left + 3, width=2, height=3)
|
||||
)
|
||||
|
||||
# Block E
|
||||
block_e_margin_left = 28
|
||||
block_e_margin_top = 20
|
||||
(grid
|
||||
.add(SeatPixel("E01", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("E01")), row=block_e_margin_top, column=block_e_margin_left, width=2, height=3)
|
||||
.add(SeatPixel("E02", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("E02")), row=block_e_margin_top + 4, column=block_e_margin_left, width=2, height=3)
|
||||
.add(SeatPixel("E03", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("E03")), row=block_e_margin_top + 8, column=block_e_margin_left, width=2, height=3)
|
||||
.add(SeatPixel("E10", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("E10")), row=block_e_margin_top, column=block_e_margin_left + 3, width=2, height=3)
|
||||
.add(SeatPixel("E11", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("E11")), row=block_e_margin_top + 4, column=block_e_margin_left + 3, width=2, height=3)
|
||||
.add(SeatPixel("E12", on_press_cb=self.seat_clicked_cb, seat=self.get_seat("E12")), row=block_e_margin_top + 8, column=block_e_margin_left + 3, width=2, height=3)
|
||||
)
|
||||
|
||||
# Middle Wall
|
||||
for y in range(0, 13):
|
||||
grid.add(WallPixel(), row=y, column=10)
|
||||
for y in range(19, MAX_GRID_HEIGHT_PIXELS):
|
||||
grid.add(WallPixel(), row=y, column=10)
|
||||
|
||||
# Stage
|
||||
for x in range(11, MAX_GRID_WIDTH_PIXELS):
|
||||
grid.add(WallPixel(), row=35, column=x)
|
||||
grid.add(TextPixel(text="Bühne"), row=36, column=11, width=24, height=9)
|
||||
|
||||
# Drinks
|
||||
grid.add(TextPixel(text="G\ne\nt\nr\nä\nn\nk\ne"), row=21, column=11, width=3, height=11)
|
||||
|
||||
# Sleeping
|
||||
grid.add(TextPixel(icon_name="material/bed"), row=1, column=1, width=4, height=11)
|
||||
|
||||
# Toilet
|
||||
grid.add(TextPixel(icon_name="material/floor", no_outline=True), row=1, column=7, width=3, height=2)
|
||||
grid.add(TextPixel(icon_name="material/north", no_outline=True), row=3, column=7, width=3, height=2)
|
||||
grid.add(TextPixel(icon_name="material/wc"), row=5, column=7, width=3, height=2)
|
||||
|
||||
# Entry/Helpdesk
|
||||
grid.add(TextPixel(text="Einlass\n &Orga"), row=19, column=3, width=7, height=5)
|
||||
|
||||
# Wall below Entry/Helpdesk
|
||||
for y in range(24, MAX_GRID_HEIGHT_PIXELS):
|
||||
grid.add(WallPixel(), row=y, column=3)
|
||||
|
||||
# Entry Arrow
|
||||
grid.add(TextPixel(icon_name="material/east", no_outline=True), row=15, column=1, width=2, height=2)
|
||||
|
||||
return Rectangle(
|
||||
content=grid,
|
||||
grow_x=True,
|
||||
grow_y=True,
|
||||
stroke_color=self.session.theme.neutral_color,
|
||||
stroke_width=0.1,
|
||||
fill=self.session.theme.primary_color,
|
||||
margin=0.5
|
||||
)
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
from decimal import Decimal
|
||||
from typing import Optional, Callable
|
||||
|
||||
from rio import Component, Column, Text, TextStyle, Button, Spacer, event
|
||||
|
||||
from src.ezgg_lan_manager import TicketingService
|
||||
from src.ezgg_lan_manager.types.UserSession import UserSession
|
||||
from rio import Component, Column, Text, TextStyle, Button, Spacer
|
||||
|
||||
|
||||
class SeatingPlanInfoBox(Component):
|
||||
@@ -15,38 +12,8 @@ class SeatingPlanInfoBox(Component):
|
||||
seat_occupant: Optional[str] = None
|
||||
seat_price: Decimal = Decimal("0")
|
||||
is_blocked: bool = False
|
||||
has_user_ticket: bool = False
|
||||
booking_button_text: str = ""
|
||||
override_text: str = "" # If this is set, all other functionality is disabled and the text is shown
|
||||
|
||||
@event.on_populate
|
||||
async def check_ticket(self) -> None:
|
||||
try:
|
||||
user_id = self.session[UserSession].user_id
|
||||
user_ticket = await self.session[TicketingService].get_user_ticket(user_id)
|
||||
self.has_user_ticket = not (user_ticket is None)
|
||||
self.booking_button_text = "Buchen" if self.has_user_ticket else "Ticket kaufen"
|
||||
self.force_refresh()
|
||||
except KeyError:
|
||||
return
|
||||
|
||||
async def purchase_clicked(self):
|
||||
if self.has_user_ticket:
|
||||
await self.purchase_cb()
|
||||
else:
|
||||
self.session.navigate_to("./buy_ticket")
|
||||
|
||||
def build(self) -> Component:
|
||||
try:
|
||||
user_id = self.session[UserSession].user_id
|
||||
except KeyError:
|
||||
user_id = None
|
||||
|
||||
if self.override_text:
|
||||
return Column(Text(self.override_text, margin=1,
|
||||
style=TextStyle(fill=self.session.theme.neutral_color, font_size=1.4), overflow="wrap",
|
||||
justify="center"), min_height=10)
|
||||
|
||||
if not self.show:
|
||||
return Spacer()
|
||||
if self.is_blocked:
|
||||
@@ -69,7 +36,7 @@ class SeatingPlanInfoBox(Component):
|
||||
style=TextStyle(fill=self.session.theme.neutral_color), overflow="wrap", justify="center"),
|
||||
Button(
|
||||
Text(
|
||||
text=self.booking_button_text,
|
||||
f"Buchen",
|
||||
margin=1,
|
||||
style=TextStyle(fill=self.session.theme.neutral_color, font_size=1.1),
|
||||
overflow="wrap",
|
||||
@@ -81,10 +48,7 @@ class SeatingPlanInfoBox(Component):
|
||||
margin=1,
|
||||
grow_y=False,
|
||||
is_sensitive=not self.is_booking_blocked,
|
||||
on_press=self.purchase_clicked
|
||||
) if user_id is not None else Text(f"Du musst eingeloggt sein um einen Sitzplatz zu buchen",
|
||||
margin=1,
|
||||
style=TextStyle(fill=self.session.theme.neutral_color),
|
||||
overflow="wrap", justify="center"),
|
||||
on_press=self.purchase_cb
|
||||
),
|
||||
min_height=10
|
||||
)
|
||||
@@ -1,61 +1,43 @@
|
||||
from functools import partial
|
||||
|
||||
from rio import Component, Text, Icon, TextStyle, Rectangle, Spacer, Color, PointerEventListener, Column, Row, PointerEvent, Tooltip
|
||||
from typing import Optional, Callable, Literal
|
||||
from rio import Component, Text, Icon, TextStyle, Rectangle, Spacer, Color, PointerEventListener, Column
|
||||
from typing import Optional, Callable
|
||||
|
||||
from src.ezgg_lan_manager.types.Seat import Seat
|
||||
from src.ezgg_lan_manager.types.UserSession import UserSession
|
||||
from src.ez_lan_manager.types.Seat import Seat
|
||||
from src.ez_lan_manager.types.SessionStorage import SessionStorage
|
||||
|
||||
|
||||
class SeatPixel(Component):
|
||||
seat_id: str
|
||||
on_press_cb: Callable
|
||||
seat: Seat
|
||||
seat_orientation: Literal["top", "bottom"]
|
||||
|
||||
def determine_color(self) -> Color:
|
||||
try:
|
||||
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:
|
||||
if self.seat.user is not None and self.seat.user.user_id == self.session[SessionStorage].user_id:
|
||||
return Color.from_hex("800080")
|
||||
elif self.seat.is_blocked or self.seat.user is not None:
|
||||
return self.session.theme.danger_color
|
||||
return self.session.theme.success_color
|
||||
|
||||
def build(self) -> Component:
|
||||
text = Text(f"{self.seat_id}", style=TextStyle(fill=self.session.theme.primary_color, font_size=0.9), align_x=0.5, selectable=False)
|
||||
rec = Rectangle(
|
||||
content=Row(text),
|
||||
min_width=1,
|
||||
min_height=1,
|
||||
fill=self.determine_color(),
|
||||
stroke_width=0.1,
|
||||
hover_stroke_width=0.1,
|
||||
stroke_color=Color.from_hex("003300") if self.seat.category == "NORMAL" else Color.from_hex("66ff99"),
|
||||
grow_x=True,
|
||||
grow_y=True,
|
||||
hover_fill=self.session.theme.hud_color,
|
||||
transition_time=0.4,
|
||||
ripple=True
|
||||
)
|
||||
|
||||
if self.seat.user or self.seat.is_blocked:
|
||||
return PointerEventListener(
|
||||
content=Tooltip(
|
||||
anchor=rec,
|
||||
tip=self.seat.user.user_name if self.seat.user else "Gesperrt",
|
||||
position=self.seat_orientation,
|
||||
return PointerEventListener(
|
||||
content=Rectangle(
|
||||
content=Column(
|
||||
Text(f"{self.seat_id}", style=TextStyle(fill=self.session.theme.primary_color, font_size=0.7), align_x=0.5, selectable=False),
|
||||
Text(f"{self.seat.category[0]}", style=TextStyle(fill=self.session.theme.primary_color, font_size=0.9), align_x=0.5, selectable=False, overflow="wrap")
|
||||
),
|
||||
on_press=partial(self.on_press_cb, self.seat_id),
|
||||
)
|
||||
else:
|
||||
return PointerEventListener(
|
||||
content=rec,
|
||||
on_press=partial(self.on_press_cb, self.seat_id),
|
||||
)
|
||||
|
||||
min_width=1,
|
||||
min_height=1,
|
||||
fill=self.determine_color(),
|
||||
hover_stroke_width = 0.1,
|
||||
grow_x=True,
|
||||
grow_y=True,
|
||||
hover_fill=self.session.theme.hud_color,
|
||||
transition_time=0.4,
|
||||
ripple=True
|
||||
),
|
||||
on_press=partial(self.on_press_cb, self.seat_id)
|
||||
)
|
||||
|
||||
class TextPixel(Component):
|
||||
text: Optional[str] = None
|
||||
@@ -76,14 +58,13 @@ class TextPixel(Component):
|
||||
fill=self.session.theme.primary_color,
|
||||
stroke_width=0.0 if self.no_outline else 0.1,
|
||||
stroke_color=self.session.theme.neutral_color,
|
||||
hover_stroke_width=None if self.no_outline else 0.1,
|
||||
hover_stroke_width = None if self.no_outline else 0.1,
|
||||
grow_x=True,
|
||||
grow_y=True,
|
||||
hover_fill=None,
|
||||
ripple=True
|
||||
)
|
||||
|
||||
|
||||
class WallPixel(Component):
|
||||
def build(self) -> Component:
|
||||
return Rectangle(
|
||||
@@ -94,7 +75,6 @@ class WallPixel(Component):
|
||||
grow_y=True,
|
||||
)
|
||||
|
||||
|
||||
class DebugPixel(Component):
|
||||
def build(self) -> Component:
|
||||
return Rectangle(
|
||||
@@ -102,15 +82,14 @@ class DebugPixel(Component):
|
||||
min_width=1,
|
||||
min_height=1,
|
||||
fill=self.session.theme.success_color,
|
||||
hover_stroke_color=self.session.theme.hud_color,
|
||||
hover_stroke_width=0.1,
|
||||
hover_stroke_color = self.session.theme.hud_color,
|
||||
hover_stroke_width = 0.1,
|
||||
grow_x=True,
|
||||
grow_y=True,
|
||||
hover_fill=self.session.theme.secondary_color,
|
||||
transition_time=0.1
|
||||
)
|
||||
|
||||
|
||||
class InvisiblePixel(Component):
|
||||
def build(self) -> Component:
|
||||
return Rectangle(
|
||||
@@ -121,4 +100,4 @@ class InvisiblePixel(Component):
|
||||
hover_stroke_width=0.0,
|
||||
grow_x=True,
|
||||
grow_y=True
|
||||
)
|
||||
)
|
||||
@@ -1,15 +1,15 @@
|
||||
from asyncio import sleep, create_task
|
||||
from decimal import Decimal
|
||||
from typing import Optional
|
||||
|
||||
from rio import Component, Column, Text, TextStyle, Button, Row, ScrollContainer, Spacer, Popup, Table, event, Card
|
||||
import rio
|
||||
from rio import Component, Column, Text, TextStyle, Button, Row, ScrollContainer, Spacer, Popup, Table
|
||||
|
||||
from src.ezgg_lan_manager.components.CateringCartItem import CateringCartItem
|
||||
from src.ezgg_lan_manager.components.CateringOrderItem import CateringOrderItem
|
||||
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.types.CateringOrder import CateringOrder, CateringMenuItemsWithAmount
|
||||
from src.ezgg_lan_manager.types.UserSession import UserSession
|
||||
from src.ez_lan_manager.components.CateringCartItem import CateringCartItem
|
||||
from src.ez_lan_manager.components.CateringOrderItem import CateringOrderItem
|
||||
from src.ez_lan_manager.services.AccountingService import AccountingService
|
||||
from src.ez_lan_manager.services.CateringService import CateringService, CateringError, CateringErrorType
|
||||
from src.ez_lan_manager.types.CateringOrder import CateringOrder, CateringMenuItemsWithAmount
|
||||
from src.ez_lan_manager.types.SessionStorage import SessionStorage
|
||||
|
||||
POPUP_CLOSE_TIMEOUT_SECONDS = 3
|
||||
|
||||
@@ -21,23 +21,13 @@ class ShoppingCartAndOrders(Component):
|
||||
popup_is_shown: bool = False
|
||||
popup_is_error: bool = True
|
||||
|
||||
@event.periodic(5)
|
||||
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 and user_id is not None:
|
||||
self.orders = await self.session[CateringService].get_orders_for_user(user_id)
|
||||
|
||||
async def switch(self) -> None:
|
||||
self.show_cart = not self.show_cart
|
||||
user_id = self._get_user_id()
|
||||
if user_id is not None:
|
||||
self.orders = await self.session[CateringService].get_orders_for_user(user_id)
|
||||
self.orders = await self.session[CateringService].get_orders_for_user(self.session[SessionStorage].user_id)
|
||||
|
||||
async def on_remove_item(self, list_id: int) -> None:
|
||||
catering_service = self.session[CateringService]
|
||||
user_id = self._get_user_id()
|
||||
if user_id is None:
|
||||
return
|
||||
user_id = self.session[SessionStorage].user_id
|
||||
cart = catering_service.get_cart(user_id)
|
||||
try:
|
||||
cart.pop(list_id)
|
||||
@@ -47,16 +37,13 @@ class ShoppingCartAndOrders(Component):
|
||||
self.force_refresh()
|
||||
|
||||
async def on_empty_cart_pressed(self) -> None:
|
||||
user_id = self._get_user_id()
|
||||
if user_id is None:
|
||||
return
|
||||
self.session[CateringService].save_cart(user_id, [])
|
||||
self.session[CateringService].save_cart(self.session[SessionStorage].user_id, [])
|
||||
self.force_refresh()
|
||||
|
||||
async def on_add_item(self, article_id: int) -> None:
|
||||
catering_service = self.session[CateringService]
|
||||
user_id = self._get_user_id()
|
||||
if user_id is None:
|
||||
user_id = self.session[SessionStorage].user_id
|
||||
if not user_id:
|
||||
return
|
||||
cart = catering_service.get_cart(user_id)
|
||||
item_to_add = await catering_service.get_menu_item_by_id(article_id)
|
||||
@@ -77,9 +64,7 @@ class ShoppingCartAndOrders(Component):
|
||||
self.order_button_loading = True
|
||||
self.force_refresh()
|
||||
|
||||
user_id = self._get_user_id()
|
||||
if user_id is None:
|
||||
return
|
||||
user_id = self.session[SessionStorage].user_id
|
||||
cart = self.session[CateringService].get_cart(user_id)
|
||||
show_popup_task = None
|
||||
if len(cart) < 1:
|
||||
@@ -100,14 +85,13 @@ class ShoppingCartAndOrders(Component):
|
||||
show_popup_task = create_task(self.show_popup("Guthaben nicht ausreichend", True))
|
||||
else:
|
||||
show_popup_task = create_task(self.show_popup("Unbekannter Fehler", True))
|
||||
else:
|
||||
self.session[CateringService].save_cart(user_id, [])
|
||||
self.session[CateringService].save_cart(self.session[SessionStorage].user_id, [])
|
||||
self.order_button_loading = False
|
||||
if not show_popup_task:
|
||||
show_popup_task = create_task(self.show_popup("Bestellung erfolgreich aufgegeben!", False))
|
||||
|
||||
async def _create_order_info_modal(self, order: CateringOrder) -> None:
|
||||
def build_dialog_content() -> Component:
|
||||
def build_dialog_content() -> rio.Component:
|
||||
# @todo: rio 0.10.8 did not have the ability to align the columns, check back in a future version
|
||||
table = Table(
|
||||
{
|
||||
@@ -117,9 +101,9 @@ class ShoppingCartAndOrders(Component):
|
||||
},
|
||||
show_row_numbers=False
|
||||
)
|
||||
return Card(
|
||||
Column(
|
||||
Text(
|
||||
return rio.Card(
|
||||
rio.Column(
|
||||
rio.Text(
|
||||
f"Deine Bestellung ({order.order_id})",
|
||||
align_x=0.5,
|
||||
margin_bottom=0.5
|
||||
@@ -140,20 +124,14 @@ class ShoppingCartAndOrders(Component):
|
||||
dialog = await self.session.show_custom_dialog(
|
||||
build=build_dialog_content,
|
||||
modal=True,
|
||||
user_closable=True,
|
||||
user_closeable=True,
|
||||
)
|
||||
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:
|
||||
user_id = self._get_user_id()
|
||||
def build(self) -> rio.Component:
|
||||
user_id = self.session[SessionStorage].user_id
|
||||
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:
|
||||
cart_container = ScrollContainer(
|
||||
content=Column(
|
||||
@@ -171,6 +149,7 @@ class ShoppingCartAndOrders(Component):
|
||||
margin=1
|
||||
)
|
||||
return Column(
|
||||
cart_container,
|
||||
Popup(
|
||||
anchor=cart_container,
|
||||
content=Text(self.popup_message, style=TextStyle(fill=self.session.theme.danger_color if self.popup_is_error else self.session.theme.success_color), overflow="wrap", margin=2, justify="center", min_width=20),
|
||||
@@ -2,11 +2,12 @@ from functools import partial
|
||||
from typing import Callable, Optional
|
||||
from decimal import Decimal
|
||||
|
||||
import rio
|
||||
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.services.AccountingService import AccountingService
|
||||
from src.ezgg_lan_manager.types.Ticket import Ticket
|
||||
from src.ez_lan_manager import TicketingService
|
||||
from src.ez_lan_manager.services.AccountingService import AccountingService
|
||||
from src.ez_lan_manager.types.Ticket import Ticket
|
||||
|
||||
|
||||
class TicketBuyCard(Component):
|
||||
@@ -21,10 +22,10 @@ class TicketBuyCard(Component):
|
||||
available_tickets: int = 0
|
||||
|
||||
@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)
|
||||
|
||||
def build(self) -> Component:
|
||||
def build(self) -> rio.Component:
|
||||
ticket_description_style = TextStyle(
|
||||
fill=self.session.theme.neutral_color,
|
||||
font_size=1.2,
|
||||
@@ -7,10 +7,10 @@ from from_root import from_root
|
||||
from rio import Component, Column, Button, Color, TextStyle, Text, TextInput, Row, Image, event, Spacer, DateInput, \
|
||||
TextInputChangeEvent, NoFileSelectedError
|
||||
|
||||
from src.ezgg_lan_manager.services.UserService import UserService, NameNotAllowedError
|
||||
from src.ezgg_lan_manager.services.ConfigurationService import ConfigurationService
|
||||
from src.ezgg_lan_manager.types.User import User
|
||||
from src.ezgg_lan_manager.types.UserSession import UserSession
|
||||
from src.ez_lan_manager.services.UserService import UserService, NameNotAllowedError
|
||||
from src.ez_lan_manager.services.ConfigurationService import ConfigurationService
|
||||
from src.ez_lan_manager.types.SessionStorage import SessionStorage
|
||||
from src.ez_lan_manager.types.User import User
|
||||
|
||||
|
||||
class UserEditForm(Component):
|
||||
@@ -35,13 +35,8 @@ class UserEditForm(Component):
|
||||
async def on_populate(self) -> None:
|
||||
await self.session.set_title(f"{self.session[ConfigurationService].get_lan_info().name} - Profil bearbeiten")
|
||||
if self.is_own_profile:
|
||||
try:
|
||||
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.user = await self.session[UserService].get_user(self.session[SessionStorage].user_id)
|
||||
self.profile_picture = await self.session[UserService].get_profile_picture(self.user.user_id)
|
||||
else:
|
||||
self.profile_picture = await self.session[UserService].get_profile_picture(self.user.user_id)
|
||||
|
||||
@@ -127,7 +122,7 @@ class UserEditForm(Component):
|
||||
|
||||
def build(self) -> Component:
|
||||
pfp_image_container = Image(
|
||||
from_root("src/ezgg_lan_manager/assets/img/anon_pfp.png") if self.profile_picture is None else self.profile_picture,
|
||||
from_root("src/ez_lan_manager/assets/img/anon_pfp.png") if self.profile_picture is None else self.profile_picture,
|
||||
align_x=0.5,
|
||||
min_width=10,
|
||||
min_height=10,
|
||||
@@ -0,0 +1,15 @@
|
||||
import logging
|
||||
|
||||
from rio import Component
|
||||
from src.ez_lan_manager.components.LoginBox import LoginBox
|
||||
from src.ez_lan_manager.components.UserInfoBox import UserInfoBox
|
||||
from src.ez_lan_manager.types.SessionStorage import SessionStorage
|
||||
|
||||
logger = logging.getLogger(__name__.split(".")[-1])
|
||||
|
||||
class UserInfoAndLoginBox(Component):
|
||||
def build(self) -> Component:
|
||||
if self.session[SessionStorage].user_id is None:
|
||||
return LoginBox(status_change_cb=self.force_refresh)
|
||||
else:
|
||||
return UserInfoBox(status_change_cb=self.force_refresh)
|
||||
@@ -4,17 +4,16 @@ from decimal import Decimal
|
||||
|
||||
from rio import Component, TextStyle, Color, Button, Text, Rectangle, Column, Row, Spacer, Link, event, EventHandler
|
||||
|
||||
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.RefreshService import RefreshService
|
||||
from src.ezgg_lan_manager.services.UserService import UserService
|
||||
from src.ezgg_lan_manager.services.AccountingService import AccountingService
|
||||
from src.ezgg_lan_manager.services.TicketingService import TicketingService
|
||||
from src.ezgg_lan_manager.services.SeatingService import SeatingService
|
||||
from src.ezgg_lan_manager.types.Seat import Seat
|
||||
from src.ezgg_lan_manager.types.Ticket import Ticket
|
||||
from src.ezgg_lan_manager.types.User import User
|
||||
from src.ezgg_lan_manager.types.UserSession import UserSession
|
||||
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.AccountingService import AccountingService
|
||||
from src.ez_lan_manager.services.TicketingService import TicketingService
|
||||
from src.ez_lan_manager.services.SeatingService import SeatingService
|
||||
from src.ez_lan_manager.types.Seat import Seat
|
||||
from src.ez_lan_manager.types.Ticket import Ticket
|
||||
from src.ez_lan_manager.types.User import User
|
||||
from src.ez_lan_manager.types.SessionStorage import SessionStorage
|
||||
|
||||
|
||||
class StatusButton(Component):
|
||||
@@ -42,7 +41,6 @@ class StatusButton(Component):
|
||||
|
||||
|
||||
class UserInfoBox(Component):
|
||||
user_id: int
|
||||
status_change_cb: EventHandler = None
|
||||
TEXT_STYLE = TextStyle(fill=Color.from_hex("02dac5"), font_size=0.9)
|
||||
user: Optional[User] = None
|
||||
@@ -55,29 +53,29 @@ class UserInfoBox(Component):
|
||||
return choice(["Guten Tacho", "Tuten Gag", "Servus", "Moinjour", "Hallöchen", "Heyho", "Moinsen"])
|
||||
|
||||
async def logout(self) -> None:
|
||||
self.session.detach(UserSession)
|
||||
await self.session[SessionStorage].clear()
|
||||
self.user = None
|
||||
self.session[LocalDataService].del_session(self.session[LocalData].stored_session_token)
|
||||
self.session[LocalData].stored_session_token = None
|
||||
self.session.attach(self.session[LocalData])
|
||||
if self.status_change_cb is not None:
|
||||
await self.status_change_cb()
|
||||
await self.session[RefreshService].trigger_refresh()
|
||||
self.session.navigate_to("")
|
||||
self.status_change_cb()
|
||||
self.session.navigate_to("/")
|
||||
|
||||
@event.on_populate
|
||||
async def async_init(self) -> None:
|
||||
self.user = await self.session[UserService].get_user(self.user_id)
|
||||
if self.user is not None:
|
||||
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_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.session[AccountingService].add_update_hook(self.update)
|
||||
self.session[AccountingService].add_update_hook(self.update)
|
||||
|
||||
async def update(self) -> None:
|
||||
self.user_balance = await self.session[AccountingService].get_balance(self.user_id)
|
||||
self.user_ticket = await self.session[TicketingService].get_user_ticket(self.user_id)
|
||||
self.user_seat = await self.session[SeatingService].get_user_seat(self.user_id)
|
||||
if not self.user:
|
||||
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_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:
|
||||
if not self.user:
|
||||
@@ -117,5 +115,5 @@ class UserInfoBox(Component):
|
||||
min_width=12,
|
||||
align_x=0.5,
|
||||
margin_top=0.3,
|
||||
margin_bottom=1.5
|
||||
margin_bottom=2
|
||||
)
|
||||
@@ -0,0 +1,24 @@
|
||||
from typing import Optional
|
||||
|
||||
from rio import URL, GuardEvent
|
||||
|
||||
from src.ez_lan_manager.services.UserService import UserService
|
||||
from src.ez_lan_manager.types.SessionStorage import SessionStorage
|
||||
|
||||
|
||||
# Guards pages against access from users that are NOT logged in
|
||||
def logged_in_guard(event: GuardEvent) -> Optional[URL]:
|
||||
if event.session[SessionStorage].user_id is None:
|
||||
return URL("./")
|
||||
|
||||
# Guards pages against access from users that ARE logged in
|
||||
def not_logged_in_guard(event: GuardEvent) -> Optional[URL]:
|
||||
if event.session[SessionStorage].user_id is not None:
|
||||
return URL("./")
|
||||
|
||||
# Guards pages against access from users that are NOT logged in and NOT team members
|
||||
def team_guard(event: GuardEvent) -> Optional[URL]:
|
||||
user_id = event.session[SessionStorage].user_id
|
||||
is_team_member = event.session[SessionStorage].is_team_member
|
||||
if user_id is None or not is_team_member:
|
||||
return URL("./")
|
||||
@@ -5,9 +5,9 @@ from decimal import Decimal
|
||||
|
||||
import sys
|
||||
|
||||
from src.ezgg_lan_manager import init_services
|
||||
from src.ezgg_lan_manager.types.CateringMenuItem import CateringMenuItemCategory
|
||||
from src.ezgg_lan_manager.types.News import News
|
||||
from src.ez_lan_manager import init_services
|
||||
from src.ez_lan_manager.types.CateringMenuItem import CateringMenuItemCategory
|
||||
from src.ez_lan_manager.types.News import News
|
||||
|
||||
DEMO_USERS = [
|
||||
{"user_name": "manfred", "user_mail": "manfred@demomail.com", "password_clear_text": "manfred"}, # Gast
|
||||
@@ -185,9 +185,9 @@ async def run() -> None:
|
||||
|
||||
await news_service.add_news(News(
|
||||
news_id=None,
|
||||
title="Der EZGG LAN Manager",
|
||||
title="Der EZ LAN Manager",
|
||||
subtitle="Eine Software des EZ GG e.V.",
|
||||
content="Dies ist eine WIP-Version des EZGG LAN Managers. Diese Software soll uns helfen in Zukunft die LAN "
|
||||
content="Dies ist eine WIP-Version des EZ LAN Managers. Diese Software soll uns helfen in Zukunft die LAN "
|
||||
"Parties des EZ GG e.V.'s zu organisieren. Wer Fehler findet darf sie behalten. (Oder er meldet "
|
||||
"sie)",
|
||||
author=user,
|
||||
@@ -1,39 +1,28 @@
|
||||
from decimal import Decimal
|
||||
from functools import partial
|
||||
from typing import Optional
|
||||
|
||||
from rio import Column, Component, event, Text, TextStyle, Button, Color, Revealer, Row, ProgressCircle, Link, Image
|
||||
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.components.MainViewContentBox import MainViewContentBox
|
||||
from src.ezgg_lan_manager.types.Transaction import Transaction
|
||||
from src.ezgg_lan_manager.types.User import User
|
||||
from src.ezgg_lan_manager.types.UserSession import UserSession
|
||||
from src.ez_lan_manager import ConfigurationService, UserService, AccountingService
|
||||
from src.ez_lan_manager.components.MainViewContentBox import MainViewContentBox
|
||||
from src.ez_lan_manager.types.SessionStorage import SessionStorage
|
||||
from src.ez_lan_manager.types.Transaction import Transaction
|
||||
from src.ez_lan_manager.types.User import User
|
||||
|
||||
|
||||
class AccountPage(Component):
|
||||
user: Optional[User] = None
|
||||
balance: Optional[Decimal] = None
|
||||
balance: Optional[int] = None
|
||||
transaction_history: list[Transaction] = list()
|
||||
payment_qr_image: bytes = None
|
||||
banking_info_revealer_open: bool = False
|
||||
paypal_info_revealer_open: bool = False
|
||||
|
||||
@event.on_populate
|
||||
async def on_populate(self) -> None:
|
||||
await self.session.set_title(f"{self.session[ConfigurationService].get_lan_info().name} - Guthabenkonto")
|
||||
try:
|
||||
user_id = self.session[UserSession].user_id
|
||||
except KeyError:
|
||||
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)
|
||||
self.payment_qr_image = self.session[AccountingService].make_payment_qr_image(
|
||||
"Einfach Zocken Gaming Gesellschaft",
|
||||
"GENODE51BIK",
|
||||
"DE47517624340019856607",
|
||||
f"AUFLADUNG - {self.user.user_id} - {self.user.user_name}")
|
||||
self.user = await self.session[UserService].get_user(self.session[SessionStorage].user_id)
|
||||
self.balance = await self.session[AccountingService].get_balance(self.user.user_id)
|
||||
self.transaction_history = await self.session[AccountingService].get_transaction_history(self.user.user_id)
|
||||
|
||||
async def _on_banking_info_press(self) -> None:
|
||||
self.banking_info_revealer_open = not self.banking_info_revealer_open
|
||||
@@ -42,7 +31,7 @@ class AccountPage(Component):
|
||||
self.paypal_info_revealer_open = not self.paypal_info_revealer_open
|
||||
|
||||
def build(self) -> Component:
|
||||
if not self.user or not self.payment_qr_image:
|
||||
if not self.user and not self.balance:
|
||||
return Column(
|
||||
MainViewContentBox(
|
||||
ProgressCircle(
|
||||
@@ -91,10 +80,6 @@ class AccountPage(Component):
|
||||
margin=0,
|
||||
margin_bottom=1,
|
||||
align_x=0.5
|
||||
),
|
||||
Image(self.payment_qr_image,
|
||||
min_width=20,
|
||||
min_height=20
|
||||
)
|
||||
),
|
||||
margin=2,
|
||||
@@ -233,20 +218,19 @@ class AccountPage(Component):
|
||||
on_press=self._on_paypal_info_press
|
||||
),
|
||||
paypal_info_revealer,
|
||||
# Disabled because people did not understand the fee's and kept charging 24.03 € to their accounts
|
||||
# Link(
|
||||
# content=Button(
|
||||
# content=Text("PAYPAL (3% Gebühr - Gewerblich)", style=TextStyle(fill=Color.from_hex("121212"), font_size=0.8), justify="center"),
|
||||
# shape="rectangle",
|
||||
# style="major",
|
||||
# color="secondary",
|
||||
# grow_x=True,
|
||||
# margin=2,
|
||||
# margin_top=0
|
||||
# ),
|
||||
# target_url="https://www.paypal.com/ncp/payment/89YMGVZ4S33RS",
|
||||
# open_in_new_tab=True
|
||||
# )
|
||||
Link(
|
||||
content=Button(
|
||||
content=Text("PAYPAL (3% Gebühr - Gewerblich)", style=TextStyle(fill=Color.from_hex("121212"), font_size=0.8), justify="center"),
|
||||
shape="rectangle",
|
||||
style="major",
|
||||
color="secondary",
|
||||
grow_x=True,
|
||||
margin=2,
|
||||
margin_top=0
|
||||
),
|
||||
target_url="https://www.paypal.com/ncp/payment/89YMGVZ4S33RS",
|
||||
open_in_new_tab=True
|
||||
)
|
||||
)
|
||||
),
|
||||
MainViewContentBox(
|
||||
@@ -0,0 +1,69 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import * # type: ignore
|
||||
|
||||
from rio import Component, event, Spacer, Card, Container, Column, Row, TextStyle, Color, Text, PageView
|
||||
|
||||
from src.ez_lan_manager import ConfigurationService, DatabaseService
|
||||
from src.ez_lan_manager.components.DesktopNavigation import DesktopNavigation
|
||||
|
||||
class BasePage(Component):
|
||||
color = "secondary"
|
||||
corner_radius = (0, 0.5, 0, 0)
|
||||
@event.periodic(60)
|
||||
async def check_db_conn(self) -> None:
|
||||
is_healthy = await self.session[DatabaseService].is_healthy()
|
||||
if not is_healthy:
|
||||
self.session.navigate_to("./db-error")
|
||||
|
||||
@event.on_window_size_change
|
||||
async def on_window_size_change(self):
|
||||
self.force_refresh()
|
||||
|
||||
def build(self) -> Component:
|
||||
content = Card(
|
||||
PageView(),
|
||||
color="secondary",
|
||||
min_width=38,
|
||||
corner_radius=(0, 0.5, 0, 0)
|
||||
)
|
||||
if self.session.window_width > 28:
|
||||
return Container(
|
||||
content=Column(
|
||||
Column(
|
||||
Row(
|
||||
Spacer(grow_x=True, grow_y=True),
|
||||
DesktopNavigation(),
|
||||
content,
|
||||
Spacer(grow_x=True, grow_y=True),
|
||||
grow_y=True
|
||||
),
|
||||
Row(
|
||||
Spacer(grow_x=True, grow_y=False),
|
||||
Card(
|
||||
content=Text(f"EZ LAN Manager Version {self.session[ConfigurationService].APP_VERSION} © EZ GG e.V.", align_x=0.5, align_y=0.5, style=TextStyle(fill=self.session.theme.primary_color, font_size=0.5)),
|
||||
color=self.session.theme.neutral_color,
|
||||
corner_radius=(0, 0, 0.5, 0.5),
|
||||
grow_x=False,
|
||||
grow_y=False,
|
||||
min_height=1.2,
|
||||
min_width=53.1,
|
||||
margin_bottom=3
|
||||
),
|
||||
Spacer(grow_x=True, grow_y=False),
|
||||
grow_y=False
|
||||
),
|
||||
margin_top=4
|
||||
)
|
||||
),
|
||||
grow_x=True,
|
||||
grow_y=True
|
||||
)
|
||||
else:
|
||||
return Text(
|
||||
"Der EZ LAN Manager wird\nauf mobilen Endgeräten nur\nim Querformat unterstützt.\nBitte drehe dein Gerät.",
|
||||
align_x=0.5,
|
||||
align_y=0.5,
|
||||
style=TextStyle(fill=Color.from_hex("FFFFFF"), font_size=0.8)
|
||||
)
|
||||
|
||||
@@ -2,14 +2,14 @@ from typing import Optional
|
||||
|
||||
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.components.MainViewContentBox import MainViewContentBox
|
||||
from src.ezgg_lan_manager.components.TicketBuyCard import TicketBuyCard
|
||||
from src.ezgg_lan_manager.services.AccountingService import InsufficientFundsError
|
||||
from src.ezgg_lan_manager.services.TicketingService import TicketNotAvailableError, UserAlreadyHasTicketError
|
||||
from src.ezgg_lan_manager.types.Ticket import Ticket
|
||||
from src.ezgg_lan_manager.types.User import User
|
||||
from src.ezgg_lan_manager.types.UserSession import UserSession
|
||||
from src.ez_lan_manager import ConfigurationService, UserService, TicketingService
|
||||
from src.ez_lan_manager.components.MainViewContentBox import MainViewContentBox
|
||||
from src.ez_lan_manager.components.TicketBuyCard import TicketBuyCard
|
||||
from src.ez_lan_manager.services.AccountingService import InsufficientFundsError
|
||||
from src.ez_lan_manager.services.TicketingService import TicketNotAvailableError, UserAlreadyHasTicketError
|
||||
from src.ez_lan_manager.types.SessionStorage import SessionStorage
|
||||
from src.ez_lan_manager.types.Ticket import Ticket
|
||||
from src.ez_lan_manager.types.User import User
|
||||
|
||||
|
||||
class BuyTicketPage(Component):
|
||||
@@ -19,24 +19,14 @@ class BuyTicketPage(Component):
|
||||
popup_message: str = ""
|
||||
is_popup_success: bool = False
|
||||
is_buying_enabled: bool = False
|
||||
is_user_logged_in: bool = False
|
||||
|
||||
@event.on_populate
|
||||
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} - Ticket kaufen")
|
||||
try:
|
||||
user_id = self.session[UserSession].user_id
|
||||
except KeyError:
|
||||
self.user = None
|
||||
else:
|
||||
self.user = await self.session[UserService].get_user(user_id)
|
||||
self.user = await self.session[UserService].get_user(self.session[SessionStorage].user_id)
|
||||
if self.user is None: # No user logged in
|
||||
self.is_buying_enabled = False
|
||||
self.is_user_logged_in = False
|
||||
self.user_ticket = None
|
||||
else: # User is logged in
|
||||
self.is_user_logged_in = True
|
||||
possible_ticket = await self.session[TicketingService].get_user_ticket(self.user.user_id)
|
||||
self.user_ticket = possible_ticket
|
||||
if possible_ticket is not None: # User already has a ticket
|
||||
@@ -77,29 +67,17 @@ class BuyTicketPage(Component):
|
||||
|
||||
def build(self) -> Component:
|
||||
ticket_infos = self.session[ConfigurationService].get_ticket_info()
|
||||
header = Column(
|
||||
Text(
|
||||
"Tickets & Preise",
|
||||
style=TextStyle(
|
||||
fill=self.session.theme.background_color,
|
||||
font_size=1.2
|
||||
),
|
||||
margin_top=2,
|
||||
align_x=0.5
|
||||
header = Text(
|
||||
"Tickets & Preise",
|
||||
style=TextStyle(
|
||||
fill=self.session.theme.background_color,
|
||||
font_size=1.2
|
||||
),
|
||||
spacing=0.2
|
||||
margin_top=2,
|
||||
margin_bottom=2,
|
||||
align_x=0.5
|
||||
)
|
||||
|
||||
if not self.is_user_logged_in:
|
||||
header.add(Text(
|
||||
"Du musst eingeloggt sein\num ein Ticket zu kaufen",
|
||||
style=TextStyle(
|
||||
fill=self.session.theme.background_color,
|
||||
font_size=0.6
|
||||
),
|
||||
align_x=0.5
|
||||
))
|
||||
|
||||
return Column(
|
||||
MainViewContentBox(
|
||||
Column(
|
||||
@@ -1,14 +1,13 @@
|
||||
from typing import Optional
|
||||
from typing import Optional, Callable
|
||||
|
||||
from rio import Column, Component, event, TextStyle, Text, Spacer, Revealer, SwitcherBar, SwitcherBarChangeEvent, ProgressCircle
|
||||
|
||||
from src.ezgg_lan_manager import ConfigurationService, CateringService
|
||||
from src.ezgg_lan_manager.components.CateringSelectionItem import CateringSelectionItem
|
||||
from src.ezgg_lan_manager.components.MainViewContentBox import MainViewContentBox
|
||||
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.UserSession import UserSession
|
||||
from src.ez_lan_manager import ConfigurationService, CateringService
|
||||
from src.ez_lan_manager.components.CateringSelectionItem import CateringSelectionItem
|
||||
from src.ez_lan_manager.components.MainViewContentBox import MainViewContentBox
|
||||
from src.ez_lan_manager.components.ShoppingCartAndOrders import ShoppingCartAndOrders
|
||||
from src.ez_lan_manager.types.CateringMenuItem import CateringMenuItemCategory, CateringMenuItem
|
||||
from src.ez_lan_manager.types.SessionStorage import SessionStorage
|
||||
|
||||
|
||||
class CateringPage(Component):
|
||||
@@ -16,6 +15,9 @@ class CateringPage(Component):
|
||||
all_menu_items: Optional[list[CateringMenuItem]] = None
|
||||
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
|
||||
async def on_populate(self) -> None:
|
||||
await self.session.set_title(f"{self.session[ConfigurationService].get_lan_info().name} - Catering")
|
||||
@@ -32,10 +34,7 @@ class CateringPage(Component):
|
||||
return list(filter(lambda item: item.category == category, all_menu_items))
|
||||
|
||||
def build(self) -> Component:
|
||||
try:
|
||||
user_id = self.session[UserSession].user_id
|
||||
except KeyError:
|
||||
user_id = None
|
||||
user_id = self.session[SessionStorage].user_id
|
||||
if len(self.shopping_cart_and_orders) == 0:
|
||||
self.shopping_cart_and_orders.append(ShoppingCartAndOrders())
|
||||
if len(self.shopping_cart_and_orders) > 1:
|
||||
@@ -3,93 +3,94 @@ from typing import Optional
|
||||
|
||||
from rio import Text, Column, TextStyle, Component, event, TextInput, MultiLineTextInput, Row, Button
|
||||
|
||||
from src.ezgg_lan_manager import ConfigurationService, UserService, MailingService
|
||||
from src.ezgg_lan_manager.components.MainViewContentBox import MainViewContentBox
|
||||
from src.ezgg_lan_manager.types.User import User
|
||||
from src.ezgg_lan_manager.types.UserSession import UserSession
|
||||
from src.ez_lan_manager import ConfigurationService, UserService, MailingService
|
||||
from src.ez_lan_manager.components.AnimatedText import AnimatedText
|
||||
from src.ez_lan_manager.components.MainViewContentBox import MainViewContentBox
|
||||
from src.ez_lan_manager.types.SessionStorage import SessionStorage
|
||||
from src.ez_lan_manager.types.User import User
|
||||
|
||||
|
||||
class ContactPage(Component):
|
||||
# Workaround: Can not reassign this value without rio triggering refresh
|
||||
# Using list to bypass this behavior
|
||||
last_message_sent: list[datetime] = [datetime(day=1, month=1, year=2000)]
|
||||
display_printing: list[bool] = [False]
|
||||
user: Optional[User] = None
|
||||
|
||||
e_mail: str = ""
|
||||
subject: str = ""
|
||||
message: str = ""
|
||||
submit_button_is_loading: bool = False
|
||||
response_message: str = ""
|
||||
is_success: bool = True
|
||||
|
||||
@event.on_populate
|
||||
async def on_populate(self) -> None:
|
||||
await self.session.set_title(f"{self.session[ConfigurationService].get_lan_info().name} - Kontakt")
|
||||
try:
|
||||
self.user = await self.session[UserService].get_user(self.session[UserSession].user_id)
|
||||
except KeyError:
|
||||
if self.session[SessionStorage].user_id is not None:
|
||||
self.user = await self.session[UserService].get_user(self.session[SessionStorage].user_id)
|
||||
else:
|
||||
self.user = None
|
||||
self.e_mail = "" if not self.user else self.user.user_mail
|
||||
|
||||
async def on_send_pressed(self) -> None:
|
||||
error_msg = ""
|
||||
self.submit_button_is_loading = True
|
||||
self.submit_button.is_loading = True
|
||||
self.submit_button.force_refresh()
|
||||
now = datetime.now()
|
||||
if not self.e_mail:
|
||||
if not self.email_input.text:
|
||||
error_msg = "E-Mail darf nicht leer sein!"
|
||||
elif not self.subject:
|
||||
elif not self.subject_input.text:
|
||||
error_msg = "Betreff darf nicht leer sein!"
|
||||
elif not self.message:
|
||||
elif not self.message_input.text:
|
||||
error_msg = "Nachricht darf nicht leer sein!"
|
||||
elif (now - self.last_message_sent[0]) < timedelta(minutes=1):
|
||||
error_msg = "Immer mit der Ruhe!"
|
||||
|
||||
if error_msg:
|
||||
self.submit_button_is_loading = False
|
||||
self.is_success = False
|
||||
self.response_message = error_msg
|
||||
self.submit_button.is_loading = False
|
||||
await self.animated_text.display_text(False, error_msg)
|
||||
return
|
||||
|
||||
mail_recipient = self.session[ConfigurationService].get_lan_info().organizer_mail
|
||||
msg = (f"Kontaktformular vom {now.strftime('%d.%m.%Y %H:%M')}:\n\n"
|
||||
f"Betreff: {self.subject}\n"
|
||||
f"Absender: {self.e_mail}\n\n"
|
||||
f"Betreff: {self.subject_input.text}\n"
|
||||
f"Absender: {self.email_input.text}\n\n"
|
||||
f"Inhalt:\n"
|
||||
f"{self.message}\n")
|
||||
f"{self.message_input.text}\n")
|
||||
|
||||
await self.session[MailingService].send_email("Kontaktformular-Mitteilung", msg, mail_recipient)
|
||||
self.last_message_sent[0] = datetime.now()
|
||||
self.submit_button_is_loading = False
|
||||
self.is_success = True
|
||||
self.response_message = "Nachricht erfolgreich gesendet!"
|
||||
self.submit_button.is_loading = False
|
||||
await self.animated_text.display_text(True, "Nachricht erfolgreich gesendet!")
|
||||
|
||||
def build(self) -> Component:
|
||||
email_input = TextInput(
|
||||
self.animated_text = AnimatedText(
|
||||
margin_top=2,
|
||||
margin_bottom=1,
|
||||
align_x=0.1
|
||||
)
|
||||
|
||||
self.email_input = TextInput(
|
||||
label="E-Mail Adresse",
|
||||
text=self.bind().e_mail,
|
||||
text="" if not self.user else self.user.user_mail,
|
||||
margin_left=1,
|
||||
margin_right=1,
|
||||
margin_bottom=1,
|
||||
grow_x=True
|
||||
)
|
||||
|
||||
subject_input = TextInput(
|
||||
self.subject_input = TextInput(
|
||||
label="Betreff",
|
||||
text=self.bind().subject,
|
||||
text="",
|
||||
margin_left=1,
|
||||
margin_right=1,
|
||||
margin_bottom=1,
|
||||
grow_x=True
|
||||
)
|
||||
|
||||
message_input = MultiLineTextInput(
|
||||
self.message_input = MultiLineTextInput(
|
||||
label="Deine Nachricht an uns",
|
||||
text=self.bind().message,
|
||||
text="",
|
||||
margin_left=1,
|
||||
margin_right=1,
|
||||
margin_bottom=1,
|
||||
min_height=5
|
||||
)
|
||||
|
||||
submit_button = Button(
|
||||
self.submit_button = Button(
|
||||
content=Text(
|
||||
"Absenden",
|
||||
style=TextStyle(fill=self.session.theme.success_color, font_size=0.9),
|
||||
@@ -101,8 +102,7 @@ class ContactPage(Component):
|
||||
shape="rectangle",
|
||||
style="major",
|
||||
color="primary",
|
||||
on_press=self.on_send_pressed,
|
||||
is_loading=self.bind().submit_button_is_loading
|
||||
on_press=self.on_send_pressed
|
||||
)
|
||||
return Column(
|
||||
MainViewContentBox(
|
||||
@@ -117,21 +117,12 @@ class ContactPage(Component):
|
||||
margin_bottom=1,
|
||||
align_x=0.5
|
||||
),
|
||||
email_input,
|
||||
subject_input,
|
||||
message_input,
|
||||
self.email_input,
|
||||
self.subject_input,
|
||||
self.message_input,
|
||||
Row(
|
||||
Text(
|
||||
text=self.bind().response_message,
|
||||
style=TextStyle(
|
||||
fill=self.session.theme.success_color if self.is_success else self.session.theme.danger_color,
|
||||
font_size=0.9
|
||||
),
|
||||
margin_top=2,
|
||||
margin_bottom=1,
|
||||
align_x=0.1
|
||||
),
|
||||
submit_button,
|
||||
self.animated_text,
|
||||
self.submit_button,
|
||||
)
|
||||
)
|
||||
),
|
||||
@@ -5,9 +5,9 @@ from typing import * # type: ignore
|
||||
|
||||
from rio import Component, event, Spacer, Card, Container, Column, Row, TextStyle, Color, Text
|
||||
|
||||
from src.ezgg_lan_manager.services.DatabaseService import DatabaseService
|
||||
from src.ezgg_lan_manager import ConfigurationService
|
||||
from src.ezgg_lan_manager.components.MainViewContentBox import MainViewContentBox
|
||||
from src.ez_lan_manager.services.DatabaseService import DatabaseService
|
||||
from src.ez_lan_manager import ConfigurationService
|
||||
from src.ez_lan_manager.components.MainViewContentBox import MainViewContentBox
|
||||
|
||||
|
||||
class DbErrorPage(Component):
|
||||
@@ -62,7 +62,7 @@ class DbErrorPage(Component):
|
||||
Row(
|
||||
Spacer(grow_x=True, grow_y=False),
|
||||
Card(
|
||||
content=Text(f"EZGG LAN Manager Version {self.session[ConfigurationService].APP_VERSION} © EZ GG e.V.", align_x=0.5, align_y=0.5, style=TextStyle(fill=self.session.theme.primary_color, font_size=0.5)),
|
||||
content=Text(f"EZ LAN Manager Version {self.session[ConfigurationService].APP_VERSION} © EZ GG e.V.", align_x=0.5, align_y=0.5, style=TextStyle(fill=self.session.theme.primary_color, font_size=0.5)),
|
||||
color=self.session.theme.neutral_color,
|
||||
corner_radius=(0, 0, 0.5, 0.5),
|
||||
grow_x=False,
|
||||
@@ -82,7 +82,7 @@ class DbErrorPage(Component):
|
||||
)
|
||||
else:
|
||||
return Text(
|
||||
"Der EZGG LAN Manager wird\nauf mobilen Endgeräten nur\nim Querformat unterstützt.\nBitte drehe dein Gerät.",
|
||||
"Der EZ LAN Manager wird\nauf mobilen Endgeräten nur\nim Querformat unterstützt.\nBitte drehe dein Gerät.",
|
||||
align_x=0.5,
|
||||
align_y=0.5,
|
||||
style=TextStyle(fill=Color.from_hex("FFFFFF"), font_size=0.8)
|
||||
@@ -0,0 +1,26 @@
|
||||
from typing import Optional
|
||||
|
||||
from rio import Column, Component, event, Spacer
|
||||
|
||||
from src.ez_lan_manager import ConfigurationService, UserService
|
||||
from src.ez_lan_manager.components.MainViewContentBox import MainViewContentBox
|
||||
from src.ez_lan_manager.components.UserEditForm import UserEditForm
|
||||
from src.ez_lan_manager.types.SessionStorage import SessionStorage
|
||||
from src.ez_lan_manager.types.User import User
|
||||
|
||||
|
||||
class EditProfilePage(Component):
|
||||
user: Optional[User] = None
|
||||
pfp: Optional[bytes] = None
|
||||
|
||||
@event.on_populate
|
||||
async def on_populate(self) -> None:
|
||||
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:
|
||||
return Column(
|
||||
MainViewContentBox(UserEditForm(is_own_profile=True)),
|
||||
Spacer(grow_y=True)
|
||||
)
|
||||
@@ -1,7 +1,7 @@
|
||||
from rio import Column, Component, event, TextStyle, Text, Revealer
|
||||
|
||||
from src.ezgg_lan_manager import ConfigurationService
|
||||
from src.ezgg_lan_manager.components.MainViewContentBox import MainViewContentBox
|
||||
from src.ez_lan_manager import ConfigurationService
|
||||
from src.ez_lan_manager.components.MainViewContentBox import MainViewContentBox
|
||||
|
||||
FAQ: list[list[str]] = [
|
||||
["Wie melde ich mich für die LAN an?",
|
||||
@@ -4,8 +4,8 @@ from random import choices
|
||||
from email_validator import validate_email, EmailNotValidError
|
||||
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.components.MainViewContentBox import MainViewContentBox
|
||||
from src.ez_lan_manager import ConfigurationService, UserService, MailingService
|
||||
from src.ez_lan_manager.components.MainViewContentBox import MainViewContentBox
|
||||
|
||||
|
||||
class ForgotPasswordPage(Component):
|
||||
@@ -27,11 +27,11 @@ class ForgotPasswordPage(Component):
|
||||
user = await user_service.get_user(self.email_input.text.strip())
|
||||
if user is not None:
|
||||
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 mailing_service.send_email(
|
||||
subject=f"Dein neues Passwort für {lan_info.name}",
|
||||
body=f"Du hast für den EZGG-LAN Manager der {lan_info.name} ein neues Passwort angefragt. "
|
||||
body=f"Du hast für den EZ-LAN Manager der {lan_info.name} ein neues Passwort angefragt. "
|
||||
f"Und hier ist es schon:\n\n{new_password}\n\nSolltest du kein neues Passwort angefordert haben, "
|
||||
f"ignoriere diese E-Mail.\n\nLiebe Grüße\nDein {lan_info.name} - Team",
|
||||
receiver=self.email_input.text.strip()
|
||||
@@ -2,10 +2,10 @@ from typing import Optional
|
||||
|
||||
from rio import Column, Component, event, TextStyle, Text, Button, Row, TextInput, Spacer, TextInputChangeEvent
|
||||
|
||||
from src.ezgg_lan_manager import ConfigurationService, UserService, TicketingService, SeatingService
|
||||
from src.ezgg_lan_manager.components.MainViewContentBox import MainViewContentBox
|
||||
from src.ezgg_lan_manager.types.Seat import Seat
|
||||
from src.ezgg_lan_manager.types.User import User
|
||||
from src.ez_lan_manager import ConfigurationService, UserService, TicketingService, SeatingService
|
||||
from src.ez_lan_manager.components.MainViewContentBox import MainViewContentBox
|
||||
from src.ez_lan_manager.types.Seat import Seat
|
||||
from src.ez_lan_manager.types.User import User
|
||||
|
||||
|
||||
class GuestsPage(Component):
|
||||
@@ -1,7 +1,7 @@
|
||||
from rio import Text, Column, TextStyle, Component, event, Link, Color
|
||||
|
||||
from src.ezgg_lan_manager import ConfigurationService
|
||||
from src.ezgg_lan_manager.components.MainViewContentBox import MainViewContentBox
|
||||
from src.ez_lan_manager import ConfigurationService
|
||||
from src.ez_lan_manager.components.MainViewContentBox import MainViewContentBox
|
||||
|
||||
|
||||
class ImprintPage(Component):
|
||||
@@ -5,11 +5,11 @@ from typing import Optional, Callable
|
||||
|
||||
from rio import Column, Component, event, TextStyle, Text, Spacer, PointerEvent, Button, Popup, Card, Row
|
||||
|
||||
from src.ezgg_lan_manager import ConfigurationService, CateringService, SeatingService, AccountingService
|
||||
from src.ezgg_lan_manager.components.CateringManagementOrderDisplay import CateringManagementOrderDisplay
|
||||
from src.ezgg_lan_manager.components.MainViewContentBox import MainViewContentBox
|
||||
from src.ezgg_lan_manager.types.CateringOrder import CateringOrder, CateringOrderStatus
|
||||
from src.ezgg_lan_manager.types.Seat import Seat
|
||||
from src.ez_lan_manager import ConfigurationService, CateringService, SeatingService, AccountingService
|
||||
from src.ez_lan_manager.components.CateringManagementOrderDisplay import CateringManagementOrderDisplay
|
||||
from src.ez_lan_manager.components.MainViewContentBox import MainViewContentBox
|
||||
from src.ez_lan_manager.types.CateringOrder import CateringOrder, CateringOrderStatus
|
||||
from src.ez_lan_manager.types.Seat import Seat
|
||||
|
||||
logger = logging.getLogger(__name__.split(".")[-1])
|
||||
|
||||
@@ -5,11 +5,11 @@ from time import strptime
|
||||
|
||||
from rio import Column, Component, event, TextStyle, Text
|
||||
|
||||
from src.ezgg_lan_manager import ConfigurationService, UserService
|
||||
from src.ezgg_lan_manager.components.MainViewContentBox import MainViewContentBox
|
||||
from src.ezgg_lan_manager.components.NewsPost import EditableNewsPost
|
||||
from src.ezgg_lan_manager.services.NewsService import NewsService
|
||||
from src.ezgg_lan_manager.types.News import News
|
||||
from src.ez_lan_manager import ConfigurationService, UserService
|
||||
from src.ez_lan_manager.components.MainViewContentBox import MainViewContentBox
|
||||
from src.ez_lan_manager.components.NewsPost import EditableNewsPost
|
||||
from src.ez_lan_manager.services.NewsService import NewsService
|
||||
from src.ez_lan_manager.types.News import News
|
||||
|
||||
logger = logging.getLogger(__name__.split(".")[-1])
|
||||
|
||||
@@ -78,8 +78,8 @@ class ManageNewsPage(Component):
|
||||
Column(
|
||||
Text(
|
||||
text="News Verwaltung",
|
||||
fill=self.session.theme.background_color,
|
||||
style=TextStyle(
|
||||
fill=self.session.theme.background_color,
|
||||
font_size=1.2
|
||||
),
|
||||
margin_top=2,
|
||||
@@ -88,8 +88,8 @@ class ManageNewsPage(Component):
|
||||
),
|
||||
Text(
|
||||
text="Neuen News Post erstellen",
|
||||
fill=self.session.theme.background_color,
|
||||
style=TextStyle(
|
||||
fill=self.session.theme.background_color,
|
||||
font_size=1.1
|
||||
),
|
||||
margin_top=2,
|
||||
@@ -106,8 +106,8 @@ class ManageNewsPage(Component):
|
||||
),
|
||||
Text(
|
||||
text="Post erfolgreich erstellt",
|
||||
fill=self.session.theme.success_color,
|
||||
style=TextStyle(
|
||||
fill=self.session.theme.success_color,
|
||||
font_size=0.7 if self.show_success_message else 0
|
||||
),
|
||||
margin_top=0.1,
|
||||
@@ -116,8 +116,8 @@ class ManageNewsPage(Component):
|
||||
),
|
||||
Text(
|
||||
text="Bisherige Posts",
|
||||
fill=self.session.theme.background_color,
|
||||
style=TextStyle(
|
||||
fill=self.session.theme.background_color,
|
||||
font_size=1.1
|
||||
),
|
||||
margin_top=2,
|
||||
@@ -0,0 +1,32 @@
|
||||
import logging
|
||||
|
||||
from rio import Column, Component, event, TextStyle, Text, Spacer
|
||||
|
||||
from src.ez_lan_manager import ConfigurationService
|
||||
from src.ez_lan_manager.components.MainViewContentBox import MainViewContentBox
|
||||
|
||||
logger = logging.getLogger(__name__.split(".")[-1])
|
||||
|
||||
class ManageTournamentsPage(Component):
|
||||
@event.on_populate
|
||||
async def on_populate(self) -> None:
|
||||
await self.session.set_title(f"{self.session[ConfigurationService].get_lan_info().name} - Turnier Verwaltung")
|
||||
|
||||
def build(self) -> Component:
|
||||
return Column(
|
||||
MainViewContentBox(
|
||||
Column(
|
||||
Text(
|
||||
text="Turnier Verwaltung",
|
||||
style=TextStyle(
|
||||
fill=self.session.theme.background_color,
|
||||
font_size=1.2
|
||||
),
|
||||
margin_top=2,
|
||||
margin_bottom=2,
|
||||
align_x=0.5
|
||||
)
|
||||
)
|
||||
),
|
||||
Spacer()
|
||||
)
|
||||
@@ -3,17 +3,17 @@ from dataclasses import field
|
||||
from typing import Optional
|
||||
|
||||
from rio import Column, Component, event, TextStyle, Text, TextInput, ThemeContextSwitcher, Grid, \
|
||||
PointerEventListener, PointerEvent, Rectangle, Color, TextInputChangeEvent, Spacer, Row, Switch, \
|
||||
PointerEventListener, PointerEvent, Rectangle, CursorStyle, Color, TextInputChangeEvent, Spacer, Row, Switch, \
|
||||
SwitchChangeEvent, EventHandler
|
||||
|
||||
from src.ezgg_lan_manager import ConfigurationService, UserService, AccountingService, SeatingService, MailingService
|
||||
from src.ezgg_lan_manager.components.MainViewContentBox import MainViewContentBox
|
||||
from src.ezgg_lan_manager.components.NewTransactionForm import NewTransactionForm
|
||||
from src.ezgg_lan_manager.components.UserEditForm import UserEditForm
|
||||
from src.ezgg_lan_manager.services.AccountingService import InsufficientFundsError
|
||||
from src.ezgg_lan_manager.types.Transaction import Transaction
|
||||
from src.ezgg_lan_manager.types.User import User
|
||||
from src.ezgg_lan_manager.types.UserSession import UserSession
|
||||
from src.ez_lan_manager import ConfigurationService, UserService, AccountingService, SeatingService
|
||||
from src.ez_lan_manager.components.MainViewContentBox import MainViewContentBox
|
||||
from src.ez_lan_manager.components.NewTransactionForm import NewTransactionForm
|
||||
from src.ez_lan_manager.components.UserEditForm import UserEditForm
|
||||
from src.ez_lan_manager.services.AccountingService import InsufficientFundsError
|
||||
from src.ez_lan_manager.types.SessionStorage import SessionStorage
|
||||
from src.ez_lan_manager.types.Transaction import Transaction
|
||||
from src.ez_lan_manager.types.User import User
|
||||
|
||||
logger = logging.getLogger(__name__.split(".")[-1])
|
||||
|
||||
@@ -42,7 +42,7 @@ class ClickableGridContent(Component):
|
||||
grow_x=True
|
||||
),
|
||||
fill=Color.TRANSPARENT,
|
||||
cursor="pointer"
|
||||
cursor=CursorStyle.POINTER
|
||||
),
|
||||
on_pointer_enter=self.on_mouse_enter,
|
||||
on_pointer_leave=self.on_mouse_leave,
|
||||
@@ -84,11 +84,7 @@ class ManageUsersPage(Component):
|
||||
await self.session[UserService].update_user(self.selected_user)
|
||||
|
||||
async def on_new_transaction(self, transaction: Transaction) -> None:
|
||||
try:
|
||||
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:
|
||||
if not self.session[SessionStorage].is_team_member: # Better safe than sorry
|
||||
return
|
||||
|
||||
logger.info(f"Got new transaction for user with ID '{transaction.user_id}' over "
|
||||
@@ -108,17 +104,11 @@ class ManageUsersPage(Component):
|
||||
self.accounting_section_result_success = False
|
||||
return
|
||||
else:
|
||||
new_total_balance = await self.session[AccountingService].add_balance(
|
||||
await self.session[AccountingService].add_balance(
|
||||
transaction.user_id,
|
||||
transaction.value,
|
||||
transaction.reference
|
||||
)
|
||||
user = await self.session[UserService].get_user(transaction.user_id)
|
||||
await self.session[MailingService].send_email(
|
||||
"Dein Guthaben wurde aufgeladen",
|
||||
self.session[MailingService].generate_account_balance_added_mail_body(user, transaction.value, new_total_balance),
|
||||
user.user_mail
|
||||
)
|
||||
|
||||
self.accounting_section_result_text = f"Guthaben {'entfernt' if transaction.is_debit else 'hinzugefügt'}!"
|
||||
self.accounting_section_result_success = True
|
||||
@@ -1,8 +1,8 @@
|
||||
from rio import Column, Component, event
|
||||
|
||||
from src.ezgg_lan_manager import ConfigurationService, NewsService
|
||||
from src.ezgg_lan_manager.components.NewsPost import NewsPost
|
||||
from src.ezgg_lan_manager.types.News import News
|
||||
from src.ez_lan_manager import ConfigurationService, NewsService
|
||||
from src.ez_lan_manager.components.NewsPost import NewsPost
|
||||
from src.ez_lan_manager.types.News import News
|
||||
|
||||
|
||||
class NewsPage(Component):
|
||||
@@ -1,7 +1,7 @@
|
||||
from rio import Column, Component, event
|
||||
|
||||
from src.ezgg_lan_manager import ConfigurationService
|
||||
from src.ezgg_lan_manager.components.NewsPost import NewsPost
|
||||
from src.ez_lan_manager import ConfigurationService
|
||||
from src.ez_lan_manager.components.NewsPost import NewsPost
|
||||
|
||||
|
||||
class PlaceholderPage(Component):
|
||||
@@ -0,0 +1,178 @@
|
||||
import logging
|
||||
|
||||
from email_validator import validate_email, EmailNotValidError
|
||||
from rio import Column, Component, event, Text, TextStyle, TextInput, TextInputChangeEvent, Button
|
||||
|
||||
from src.ez_lan_manager import ConfigurationService, UserService, MailingService
|
||||
from src.ez_lan_manager.components.AnimatedText import AnimatedText
|
||||
from src.ez_lan_manager.components.MainViewContentBox import MainViewContentBox
|
||||
|
||||
MINIMUM_PASSWORD_LENGTH = 6
|
||||
|
||||
logger = logging.getLogger(__name__.split(".")[-1])
|
||||
|
||||
|
||||
class RegisterPage(Component):
|
||||
def on_pw_change(self, _: TextInputChangeEvent) -> None:
|
||||
if not (self.pw_1.text == self.pw_2.text) or len(self.pw_1.text) < MINIMUM_PASSWORD_LENGTH:
|
||||
self.pw_1.is_valid = False
|
||||
self.pw_2.is_valid = False
|
||||
return
|
||||
self.pw_1.is_valid = True
|
||||
self.pw_2.is_valid = True
|
||||
|
||||
def on_email_changed(self, change_event: TextInputChangeEvent) -> None:
|
||||
try:
|
||||
validate_email(change_event.text, check_deliverability=False)
|
||||
self.email_input.is_valid = True
|
||||
except EmailNotValidError:
|
||||
self.email_input.is_valid = False
|
||||
|
||||
def on_user_name_input_change(self, _: TextInputChangeEvent) -> None:
|
||||
current_text = self.user_name_input.text
|
||||
if len(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:
|
||||
self.submit_button.is_loading = True
|
||||
self.submit_button.force_refresh()
|
||||
|
||||
if len(self.user_name_input.text) < 1:
|
||||
await self.animated_text.display_text(False, "Nutzername darf nicht leer sein!")
|
||||
self.submit_button.is_loading = False
|
||||
return
|
||||
|
||||
if not (self.pw_1.text == self.pw_2.text):
|
||||
await self.animated_text.display_text(False, "Passwörter stimmen nicht überein!")
|
||||
self.submit_button.is_loading = False
|
||||
return
|
||||
|
||||
if len(self.pw_1.text) < MINIMUM_PASSWORD_LENGTH:
|
||||
await self.animated_text.display_text(False, f"Passwort muss mindestens {MINIMUM_PASSWORD_LENGTH} Zeichen lang sein!")
|
||||
self.submit_button.is_loading = False
|
||||
return
|
||||
|
||||
if not self.email_input.is_valid or len(self.email_input.text) < 3:
|
||||
await self.animated_text.display_text(False, "E-Mail Adresse ungültig!")
|
||||
self.submit_button.is_loading = False
|
||||
return
|
||||
|
||||
user_service = self.session[UserService]
|
||||
mailing_service = self.session[MailingService]
|
||||
lan_info = self.session[ConfigurationService].get_lan_info()
|
||||
|
||||
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.animated_text.display_text(False, "Benutzername oder E-Mail bereits regestriert!")
|
||||
self.submit_button.is_loading = False
|
||||
return
|
||||
|
||||
try:
|
||||
new_user = await user_service.create_user(self.user_name_input.text, self.email_input.text, self.pw_1.text)
|
||||
if not new_user:
|
||||
raise RuntimeError("User could not be created")
|
||||
except Exception as e:
|
||||
logger.error(f"Unknown error during new user registration: {e}")
|
||||
await self.animated_text.display_text(False, "Es ist ein unbekannter Fehler aufgetreten :(")
|
||||
self.submit_button.is_loading = False
|
||||
return
|
||||
|
||||
await mailing_service.send_email(
|
||||
subject="Erfolgreiche Registrierung",
|
||||
body=f"Hallo {self.user_name_input.text},\n\n"
|
||||
f"Du hast dich erfolgreich beim EZ-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"Liebe Grüße\nDein {lan_info.name} - Team",
|
||||
receiver=self.email_input.text
|
||||
)
|
||||
|
||||
self.submit_button.is_loading = False
|
||||
await self.animated_text.display_text(True, "Erfolgreich registriert!")
|
||||
|
||||
@event.on_populate
|
||||
async def on_populate(self) -> None:
|
||||
await self.session.set_title(f"{self.session[ConfigurationService].get_lan_info().name} - Registrieren")
|
||||
|
||||
def build(self) -> Component:
|
||||
self.user_name_input = TextInput(
|
||||
label="Benutzername",
|
||||
text="",
|
||||
margin_left=1,
|
||||
margin_right=1,
|
||||
margin_bottom=1,
|
||||
grow_x=True,
|
||||
on_change=self.on_user_name_input_change
|
||||
)
|
||||
self.email_input = TextInput(
|
||||
label="E-Mail Adresse",
|
||||
text="",
|
||||
margin_left=1,
|
||||
margin_right=1,
|
||||
margin_bottom=1,
|
||||
grow_x=True,
|
||||
on_change=self.on_email_changed
|
||||
)
|
||||
self.pw_1 = TextInput(
|
||||
label="Passwort",
|
||||
text="",
|
||||
margin_left=1,
|
||||
margin_right=1,
|
||||
margin_bottom=1,
|
||||
grow_x=True,
|
||||
is_secret=True,
|
||||
on_change=self.on_pw_change
|
||||
)
|
||||
self.pw_2 = TextInput(
|
||||
label="Passwort wiederholen",
|
||||
text="",
|
||||
margin_left=1,
|
||||
margin_right=1,
|
||||
margin_bottom=1,
|
||||
grow_x=True,
|
||||
is_secret=True,
|
||||
on_change=self.on_pw_change
|
||||
)
|
||||
self.submit_button = Button(
|
||||
content=Text(
|
||||
"Registrieren",
|
||||
style=TextStyle(fill=self.session.theme.background_color, font_size=0.9),
|
||||
align_x=0.5
|
||||
),
|
||||
grow_x=True,
|
||||
margin_top=2,
|
||||
margin_left=1,
|
||||
margin_right=1,
|
||||
margin_bottom=1,
|
||||
shape="rectangle",
|
||||
style="minor",
|
||||
color=self.session.theme.secondary_color,
|
||||
on_press=self.on_submit_button_pressed
|
||||
)
|
||||
self.animated_text = AnimatedText(
|
||||
margin_top=2,
|
||||
margin_left=1,
|
||||
margin_right=1,
|
||||
margin_bottom=2
|
||||
)
|
||||
return Column(
|
||||
MainViewContentBox(
|
||||
content=Column(
|
||||
Text(
|
||||
"Neues Konto anlegen",
|
||||
style=TextStyle(
|
||||
fill=self.session.theme.background_color,
|
||||
font_size=1.2
|
||||
),
|
||||
margin_top=2,
|
||||
margin_bottom=2,
|
||||
align_x=0.5
|
||||
),
|
||||
self.user_name_input,
|
||||
self.email_input,
|
||||
self.pw_1,
|
||||
self.pw_2,
|
||||
self.submit_button,
|
||||
self.animated_text
|
||||
)
|
||||
),
|
||||
align_y=0,
|
||||
)
|
||||
@@ -1,7 +1,7 @@
|
||||
from rio import Column, Component, event, TextStyle, Text, Revealer
|
||||
|
||||
from src.ezgg_lan_manager import ConfigurationService
|
||||
from src.ezgg_lan_manager.components.MainViewContentBox import MainViewContentBox
|
||||
from src.ez_lan_manager import ConfigurationService
|
||||
from src.ez_lan_manager.components.MainViewContentBox import MainViewContentBox
|
||||
|
||||
RULES: list[str] = [
|
||||
"Respektvolles Verhalten: Sei höflich und respektvoll gegenüber anderen Gästen und dem Team.",
|
||||
@@ -5,15 +5,15 @@ from typing import Optional
|
||||
|
||||
from rio import Text, Column, TextStyle, Component, event, PressEvent, ProgressCircle
|
||||
|
||||
from src.ezgg_lan_manager import ConfigurationService, SeatingService, TicketingService, UserService
|
||||
from src.ezgg_lan_manager.components.MainViewContentBox import MainViewContentBox
|
||||
from src.ezgg_lan_manager.components.SeatingPlan import SeatingPlan, SeatingPlanLegend
|
||||
from src.ezgg_lan_manager.components.SeatingPlanInfoBox import SeatingPlanInfoBox
|
||||
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.types.Seat import Seat
|
||||
from src.ezgg_lan_manager.types.User import User
|
||||
from src.ezgg_lan_manager.types.UserSession import UserSession
|
||||
from src.ez_lan_manager import ConfigurationService, SeatingService, TicketingService, UserService
|
||||
from src.ez_lan_manager.components.MainViewContentBox import MainViewContentBox
|
||||
from src.ez_lan_manager.components.SeatingPlan import SeatingPlan, SeatingPlanLegend
|
||||
from src.ez_lan_manager.components.SeatingPlanInfoBox import SeatingPlanInfoBox
|
||||
from src.ez_lan_manager.components.SeatingPurchaseBox import SeatingPurchaseBox
|
||||
from src.ez_lan_manager.services.SeatingService import NoTicketError, SeatNotFoundError, WrongCategoryError, SeatAlreadyTakenError
|
||||
from src.ez_lan_manager.types.Seat import Seat
|
||||
from src.ez_lan_manager.types.SessionStorage import SessionStorage
|
||||
from src.ez_lan_manager.types.User import User
|
||||
|
||||
logger = logging.getLogger(__name__.split(".")[-1])
|
||||
|
||||
@@ -30,17 +30,13 @@ class SeatingPlanPage(Component):
|
||||
purchase_box_loading: bool = False
|
||||
purchase_box_success_msg: Optional[str] = None
|
||||
purchase_box_error_msg: Optional[str] = None
|
||||
seating_info_text = ""
|
||||
is_booking_blocked: bool = False
|
||||
|
||||
@event.on_populate
|
||||
async def on_populate(self) -> None:
|
||||
await self.session.set_title(f"{self.session[ConfigurationService].get_lan_info().name} - Sitzplan")
|
||||
self.seating_info = await self.session[SeatingService].get_seating()
|
||||
try:
|
||||
self.user = await self.session[UserService].get_user(self.session[UserSession].user_id)
|
||||
except KeyError:
|
||||
self.user = None
|
||||
self.user = await self.session[UserService].get_user(self.session[SessionStorage].user_id)
|
||||
if not self.user:
|
||||
self.is_booking_blocked = True
|
||||
else:
|
||||
@@ -51,7 +47,6 @@ class SeatingPlanPage(Component):
|
||||
self.is_booking_blocked = True
|
||||
|
||||
async def on_seat_clicked(self, seat_id: str, _: PressEvent) -> None:
|
||||
self.seating_info_text = ""
|
||||
self.show_info_box = True
|
||||
self.show_purchase_box = False
|
||||
seat = next(filter(lambda s: s.seat_id == seat_id, self.seating_info), None)
|
||||
@@ -67,12 +62,6 @@ class SeatingPlanPage(Component):
|
||||
else:
|
||||
self.current_seat_occupant = None
|
||||
|
||||
async def on_info_clicked(self, text: str) -> None:
|
||||
self.show_info_box = True
|
||||
self.show_purchase_box = False
|
||||
self.current_seat_id = None
|
||||
self.seating_info_text = text
|
||||
|
||||
def set_error(self, msg: str) -> None:
|
||||
self.purchase_box_error_msg = msg
|
||||
self.purchase_box_success_msg = None
|
||||
@@ -130,7 +119,7 @@ class SeatingPlanPage(Component):
|
||||
Column(
|
||||
SeatingPlanInfoBox(seat_id=self.current_seat_id, seat_occupant=self.current_seat_occupant, seat_price=self.current_seat_price,
|
||||
is_blocked=self.current_seat_is_blocked, is_booking_blocked=self.is_booking_blocked, show=self.show_info_box,
|
||||
purchase_cb=self.on_purchase_clicked, override_text=self.seating_info_text),
|
||||
purchase_cb=self.on_purchase_clicked),
|
||||
SeatingPurchaseBox(
|
||||
show=self.show_purchase_box,
|
||||
seat_id=self.current_seat_id,
|
||||
@@ -143,7 +132,7 @@ class SeatingPlanPage(Component):
|
||||
)
|
||||
),
|
||||
MainViewContentBox(
|
||||
SeatingPlan(seat_clicked_cb=self.on_seat_clicked, seating_info=self.seating_info, info_clicked_cb=self.on_info_clicked) if self.seating_info else
|
||||
SeatingPlan(seat_clicked_cb=self.on_seat_clicked, seating_info=self.seating_info) if self.seating_info else
|
||||
Column(ProgressCircle(color=self.session.theme.secondary_color, margin=3),
|
||||
Text("Sitzplan wird geladen", style=TextStyle(fill=self.session.theme.neutral_color), align_x=0.5, margin=1))
|
||||
),
|
||||
@@ -1,7 +1,7 @@
|
||||
from rio import Column, Component, event, TextStyle, Text
|
||||
|
||||
from src.ezgg_lan_manager import ConfigurationService
|
||||
from src.ezgg_lan_manager.components.MainViewContentBox import MainViewContentBox
|
||||
from src.ez_lan_manager import ConfigurationService
|
||||
from src.ez_lan_manager.components.MainViewContentBox import MainViewContentBox
|
||||
|
||||
|
||||
class PAGENAME(Component):
|
||||
@@ -0,0 +1,38 @@
|
||||
from rio import Column, Component, event, TextStyle, Text
|
||||
|
||||
from src.ez_lan_manager import ConfigurationService
|
||||
from src.ez_lan_manager.components.MainViewContentBox import MainViewContentBox
|
||||
|
||||
|
||||
class TournamentsPage(Component):
|
||||
@event.on_populate
|
||||
async def on_populate(self) -> None:
|
||||
await self.session.set_title(f"{self.session[ConfigurationService].get_lan_info().name} - Turniere")
|
||||
|
||||
def build(self) -> Component:
|
||||
return Column(
|
||||
MainViewContentBox(
|
||||
Column(
|
||||
Text(
|
||||
text="Turniere",
|
||||
style=TextStyle(
|
||||
fill=self.session.theme.background_color,
|
||||
font_size=1.2
|
||||
),
|
||||
margin_top=2,
|
||||
margin_bottom=0,
|
||||
align_x=0.5
|
||||
),
|
||||
Text(
|
||||
text="Aktuell ist noch kein Turnierplan hinterlegt.",
|
||||
style=TextStyle(
|
||||
fill=self.session.theme.background_color,
|
||||
font_size=0.9
|
||||
),
|
||||
margin=1,
|
||||
overflow="wrap"
|
||||
)
|
||||
)
|
||||
),
|
||||
align_y=0
|
||||
)
|
||||
@@ -19,10 +19,3 @@ from .ManageNewsPage import ManageNewsPage
|
||||
from .ManageUsersPage import ManageUsersPage
|
||||
from .ManageCateringPage import ManageCateringPage
|
||||
from .ManageTournamentsPage import ManageTournamentsPage
|
||||
from .OverviewPage import OverviewPage
|
||||
from .TournamentDetailsPage import TournamentDetailsPage
|
||||
from .TournamentRulesPage import TournamentRulesPage
|
||||
from .ConwayPage import ConwayPage
|
||||
from .TeamsPage import TeamsPage
|
||||
from .AdminNavigationPage import AdminNavigationPage
|
||||
from .TournamentTreePage import TournamentTreePage
|
||||
@@ -1,13 +1,10 @@
|
||||
import io
|
||||
import logging
|
||||
import qrcode
|
||||
from collections.abc import Callable
|
||||
from datetime import datetime
|
||||
from decimal import Decimal, ROUND_DOWN
|
||||
from typing import Optional
|
||||
|
||||
from src.ezgg_lan_manager.services.DatabaseService import DatabaseService
|
||||
from src.ezgg_lan_manager.types.Transaction import Transaction
|
||||
from src.ez_lan_manager.services.DatabaseService import DatabaseService
|
||||
from src.ez_lan_manager.types.Transaction import Transaction
|
||||
|
||||
logger = logging.getLogger(__name__.split(".")[-1])
|
||||
|
||||
@@ -68,37 +65,9 @@ class AccountingService:
|
||||
return await self._db_service.get_all_transactions_for_user(user_id)
|
||||
|
||||
@staticmethod
|
||||
def make_euro_string_from_decimal(euros: Optional[Decimal]) -> str:
|
||||
def make_euro_string_from_decimal(euros: Decimal) -> str:
|
||||
"""
|
||||
Internally, all money values are euros as decimal. Only when showing them to the user we generate a string.
|
||||
"""
|
||||
if euros is None:
|
||||
return "0.00 €"
|
||||
rounded_decimal = str(euros.quantize(Decimal(".01"), rounding=ROUND_DOWN))
|
||||
return f"{rounded_decimal} €"
|
||||
|
||||
@staticmethod
|
||||
def make_payment_qr_image(beneficiary_name, beneficiary_bic, beneficiary_iban, text, amount_euros=None) -> bytes:
|
||||
text = text.replace("\n",";")
|
||||
amount_formatted = "EUR{:.2f}".format(amount_euros) if amount_euros else ""
|
||||
epc_text = f"""BCD
|
||||
002
|
||||
1
|
||||
SCT
|
||||
{beneficiary_bic}
|
||||
{beneficiary_name}
|
||||
{beneficiary_iban}
|
||||
{amount_formatted}
|
||||
|
||||
|
||||
{text}
|
||||
"""
|
||||
qr = qrcode.QRCode(
|
||||
version=6,
|
||||
error_correction=qrcode.constants.ERROR_CORRECT_M,
|
||||
)
|
||||
qr.add_data(epc_text)
|
||||
img = qr.make_image()
|
||||
img_bytes = io.BytesIO()
|
||||
img.save(img_bytes)
|
||||
return img_bytes.getvalue()
|
||||
@@ -3,12 +3,11 @@ from decimal import Decimal
|
||||
from enum import Enum
|
||||
from typing import Optional
|
||||
|
||||
from src.ezgg_lan_manager.services.AccountingService import AccountingService
|
||||
from src.ezgg_lan_manager.services.DatabaseService import DatabaseService
|
||||
from src.ezgg_lan_manager.services.UserService import UserService
|
||||
from src.ezgg_lan_manager.services.ReceiptPrintingService import ReceiptPrintingService
|
||||
from src.ezgg_lan_manager.types.CateringOrder import CateringOrder, CateringOrderStatus, CateringMenuItemsWithAmount
|
||||
from src.ezgg_lan_manager.types.CateringMenuItem import CateringMenuItem, CateringMenuItemCategory
|
||||
from src.ez_lan_manager.services.AccountingService import AccountingService
|
||||
from src.ez_lan_manager.services.DatabaseService import DatabaseService
|
||||
from src.ez_lan_manager.services.UserService import UserService
|
||||
from src.ez_lan_manager.types.CateringOrder import CateringOrder, CateringOrderStatus, CateringMenuItemsWithAmount
|
||||
from src.ez_lan_manager.types.CateringMenuItem import CateringMenuItem, CateringMenuItemCategory
|
||||
|
||||
logger = logging.getLogger(__name__.split(".")[-1])
|
||||
|
||||
@@ -26,11 +25,10 @@ class CateringError(Exception):
|
||||
|
||||
|
||||
class CateringService:
|
||||
def __init__(self, db_service: DatabaseService, accounting_service: AccountingService, user_service: UserService, receipt_printing_service: ReceiptPrintingService) -> None:
|
||||
def __init__(self, db_service: DatabaseService, accounting_service: AccountingService, user_service: UserService):
|
||||
self._db_service = db_service
|
||||
self._accounting_service = accounting_service
|
||||
self._user_service = user_service
|
||||
self._receipt_printing_service = receipt_printing_service
|
||||
self.cached_cart: dict[int, list[CateringMenuItem]] = {}
|
||||
|
||||
# ORDERS
|
||||
@@ -54,7 +52,6 @@ class CateringService:
|
||||
await self._accounting_service.remove_balance(user_id, total_price, f"CATERING - {order.order_id}")
|
||||
logger.info(
|
||||
f"User '{order.customer.user_name}' (ID:{order.customer.user_id}) ordered from catering for {self._accounting_service.make_euro_string_from_decimal(total_price)}")
|
||||
await self._receipt_printing_service.print_order(user, order)
|
||||
# await self.cancel_order(order) # ToDo: Check if commented out before commit. Un-comment to auto-cancel every placed order
|
||||
return order
|
||||
|
||||
@@ -7,8 +7,8 @@ import tomllib
|
||||
|
||||
from from_root import from_root
|
||||
|
||||
from src.ezgg_lan_manager.types.ConfigurationTypes import DatabaseConfiguration, MailingServiceConfiguration, LanInfo, \
|
||||
SeatingConfiguration, TicketInfo, ReceiptPrintingConfiguration
|
||||
from src.ez_lan_manager.types.ConfigurationTypes import DatabaseConfiguration, MailingServiceConfiguration, LanInfo, \
|
||||
SeatingConfiguration, TicketInfo
|
||||
|
||||
logger = logging.getLogger(__name__.split(".")[-1])
|
||||
|
||||
@@ -71,6 +71,15 @@ class ConfigurationService:
|
||||
logger.fatal("Error loading LAN Info, exiting...")
|
||||
sys.exit(1)
|
||||
|
||||
def get_seating_configuration(self) -> SeatingConfiguration:
|
||||
try:
|
||||
return SeatingConfiguration(
|
||||
seats=self._config["seating"]
|
||||
)
|
||||
except KeyError:
|
||||
logger.fatal("Error loading seating configuration, exiting...")
|
||||
sys.exit(1)
|
||||
|
||||
def get_ticket_info(self) -> tuple[TicketInfo, ...]:
|
||||
try:
|
||||
return tuple([TicketInfo(
|
||||
@@ -86,19 +95,6 @@ class ConfigurationService:
|
||||
logger.fatal("Error loading seating configuration, exiting...")
|
||||
sys.exit(1)
|
||||
|
||||
def get_receipt_printing_configuration(self) -> ReceiptPrintingConfiguration:
|
||||
try:
|
||||
receipt_printing_configuration = self._config["receipt_printing"]
|
||||
return ReceiptPrintingConfiguration(
|
||||
host=receipt_printing_configuration["host"],
|
||||
port=receipt_printing_configuration["port"],
|
||||
order_print_endpoint=receipt_printing_configuration["order_print_endpoint"],
|
||||
password=receipt_printing_configuration["password"]
|
||||
)
|
||||
except KeyError:
|
||||
logger.fatal("Error loading Receipt Printing Configuration, exiting...")
|
||||
sys.exit(1)
|
||||
|
||||
@property
|
||||
def APP_VERSION(self) -> str:
|
||||
return self._version
|
||||
@@ -1,24 +1,20 @@
|
||||
import logging
|
||||
|
||||
from datetime import date, datetime, UTC
|
||||
from datetime import date, datetime
|
||||
from typing import Optional
|
||||
from decimal import Decimal
|
||||
|
||||
import aiomysql
|
||||
|
||||
from src.ezgg_lan_manager.types.CateringOrder import CateringOrder
|
||||
from src.ezgg_lan_manager.types.CateringMenuItem import CateringMenuItem, CateringMenuItemCategory
|
||||
from src.ezgg_lan_manager.types.CateringOrder import CateringMenuItemsWithAmount, CateringOrderStatus
|
||||
from src.ezgg_lan_manager.types.ConfigurationTypes import DatabaseConfiguration
|
||||
from src.ezgg_lan_manager.types.News import News
|
||||
from src.ezgg_lan_manager.types.Participant import Participant
|
||||
from src.ezgg_lan_manager.types.Seat import Seat
|
||||
from src.ezgg_lan_manager.types.Team import TeamStatus, Team
|
||||
from src.ezgg_lan_manager.types.Ticket import Ticket
|
||||
from src.ezgg_lan_manager.types.Tournament import Tournament
|
||||
from src.ezgg_lan_manager.types.TournamentBase import GameTitle, TournamentFormat, TournamentStatus, ParticipantType, MatchStatus
|
||||
from src.ezgg_lan_manager.types.Transaction import Transaction
|
||||
from src.ezgg_lan_manager.types.User import User
|
||||
from src.ez_lan_manager.types.CateringOrder import CateringOrder
|
||||
from src.ez_lan_manager.types.CateringMenuItem import CateringMenuItem, CateringMenuItemCategory
|
||||
from src.ez_lan_manager.types.CateringOrder import CateringMenuItemsWithAmount, CateringOrderStatus
|
||||
from src.ez_lan_manager.types.ConfigurationTypes import DatabaseConfiguration
|
||||
from src.ez_lan_manager.types.News import News
|
||||
from src.ez_lan_manager.types.Seat import Seat
|
||||
from src.ez_lan_manager.types.Ticket import Ticket
|
||||
from src.ez_lan_manager.types.Transaction import Transaction
|
||||
from src.ez_lan_manager.types.User import User
|
||||
|
||||
logger = logging.getLogger(__name__.split(".")[-1])
|
||||
|
||||
@@ -62,8 +58,7 @@ class DatabaseService:
|
||||
password=self._database_config.db_password,
|
||||
db=self._database_config.db_name,
|
||||
minsize=1,
|
||||
maxsize=40,
|
||||
autocommit=True
|
||||
maxsize=40
|
||||
)
|
||||
except aiomysql.OperationalError:
|
||||
return False
|
||||
@@ -76,65 +71,16 @@ class DatabaseService:
|
||||
user_name=data[1],
|
||||
user_mail=data[2],
|
||||
user_password=data[3],
|
||||
user_fallback_password=data[4],
|
||||
user_first_name=data[5],
|
||||
user_last_name=data[6],
|
||||
user_birth_day=data[7],
|
||||
is_active=bool(data[8]),
|
||||
is_team_member=bool(data[9]),
|
||||
is_admin=bool(data[10]),
|
||||
created_at=data[11],
|
||||
last_updated_at=data[12]
|
||||
user_first_name=data[4],
|
||||
user_last_name=data[5],
|
||||
user_birth_day=data[6],
|
||||
is_active=bool(data[7]),
|
||||
is_team_member=bool(data[8]),
|
||||
is_admin=bool(data[9]),
|
||||
created_at=data[10],
|
||||
last_updated_at=data[11]
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _parse_tournament_format(format_as_string: str) -> TournamentFormat:
|
||||
if format_as_string == "SE_BO_1":
|
||||
return TournamentFormat.SINGLE_ELIMINATION_BO_1
|
||||
elif format_as_string == "SE_BO_3":
|
||||
return TournamentFormat.SINGLE_ELIMINATION_BO_3
|
||||
elif format_as_string == "SE_BO_5":
|
||||
return TournamentFormat.SINGLE_ELIMINATION_BO_5
|
||||
elif format_as_string == "DE_BO_1":
|
||||
return TournamentFormat.DOUBLE_ELIMINATION_BO_1
|
||||
elif format_as_string == "DE_BO_3":
|
||||
return TournamentFormat.DOUBLE_ELIMINATION_BO_3
|
||||
elif format_as_string == "DE_BO_5":
|
||||
return TournamentFormat.DOUBLE_ELIMINATION_BO_5
|
||||
elif format_as_string == "FFA":
|
||||
return TournamentFormat.FFA
|
||||
else:
|
||||
# If this happens, database is FUBAR
|
||||
raise RuntimeError(f"Unknown TournamentFormat: {format_as_string}")
|
||||
|
||||
@staticmethod
|
||||
def _parse_tournament_status(status_as_string: str) -> TournamentStatus:
|
||||
if status_as_string == "CLOSED":
|
||||
return TournamentStatus.CLOSED
|
||||
elif status_as_string == "OPEN":
|
||||
return TournamentStatus.OPEN
|
||||
elif status_as_string == "COMPLETED":
|
||||
return TournamentStatus.COMPLETED
|
||||
elif status_as_string == "CANCELED":
|
||||
return TournamentStatus.CANCELED
|
||||
elif status_as_string == "INVITE_ONLY":
|
||||
return TournamentStatus.INVITE_ONLY
|
||||
elif status_as_string == "ONGOING":
|
||||
return TournamentStatus.ONGOING
|
||||
else:
|
||||
# If this happens, database is FUBAR
|
||||
raise RuntimeError(f"Unknown TournamentStatus: {status_as_string}")
|
||||
|
||||
@staticmethod
|
||||
def _parse_participant_type(participant_type_as_string: str) -> ParticipantType:
|
||||
if participant_type_as_string == "PLAYER":
|
||||
return ParticipantType.PLAYER
|
||||
elif participant_type_as_string == "TEAM":
|
||||
return ParticipantType.TEAM
|
||||
else:
|
||||
# If this happens, database is FUBAR
|
||||
raise RuntimeError(f"Unknown ParticipantType: {participant_type_as_string}")
|
||||
|
||||
async def get_user_by_name(self, user_name: str) -> Optional[User]:
|
||||
async with self._connection_pool.acquire() as conn:
|
||||
async with conn.cursor(aiomysql.Cursor) as cursor:
|
||||
@@ -187,10 +133,10 @@ class DatabaseService:
|
||||
async with conn.cursor(aiomysql.Cursor) as cursor:
|
||||
try:
|
||||
await cursor.execute(
|
||||
"UPDATE users SET user_name=%s, user_mail=%s, user_password=%s, user_fallback_password=%s,"
|
||||
"user_first_name=%s, user_last_name=%s, user_birth_date=%s, is_active=%s, is_team_member=%s,"
|
||||
" is_admin=%s WHERE (user_id=%s)",
|
||||
(user.user_name, user.user_mail.lower(), user.user_password, user.user_fallback_password,
|
||||
"UPDATE users SET user_name=%s, user_mail=%s, user_password=%s, user_first_name=%s, "
|
||||
"user_last_name=%s, user_birth_date=%s, is_active=%s, is_team_member=%s, is_admin=%s "
|
||||
"WHERE (user_id=%s)",
|
||||
(user.user_name, user.user_mail.lower(), user.user_password,
|
||||
user.user_first_name, user.user_last_name, user.user_birth_day,
|
||||
user.is_active, user.is_team_member, user.is_admin,
|
||||
user.user_id)
|
||||
@@ -286,6 +232,7 @@ class DatabaseService:
|
||||
except aiomysql.InterfaceError:
|
||||
pool_init_result = await self.init_db_pool()
|
||||
if not pool_init_result:
|
||||
print(self._connection_pool)
|
||||
raise NoDatabaseConnectionError
|
||||
return await self.get_news(dt_start, dt_end)
|
||||
except Exception as e:
|
||||
@@ -449,7 +396,7 @@ class DatabaseService:
|
||||
pool_init_result = await self.init_db_pool()
|
||||
if not pool_init_result:
|
||||
raise NoDatabaseConnectionError
|
||||
return await self.delete_ticket(ticket_id)
|
||||
return await self.change_ticket_owner(ticket_id)
|
||||
except Exception as e:
|
||||
logger.warning(f"Error deleting ticket: {e}")
|
||||
return False
|
||||
@@ -841,362 +788,3 @@ class DatabaseService:
|
||||
return await self.remove_profile_picture(user_id)
|
||||
except Exception as e:
|
||||
logger.warning(f"Error deleting user profile picture: {e}")
|
||||
|
||||
async def get_all_tournaments(self) -> list[Tournament]:
|
||||
logger.info(f"Polling Tournaments...")
|
||||
async with self._connection_pool.acquire() as conn:
|
||||
async with conn.cursor(aiomysql.DictCursor) as cursor:
|
||||
try:
|
||||
await cursor.execute(
|
||||
"""
|
||||
SELECT
|
||||
/* =======================
|
||||
Tournament
|
||||
======================= */
|
||||
t.id AS tournament_id,
|
||||
t.name AS tournament_name,
|
||||
t.description AS tournament_description,
|
||||
t.format AS tournament_format,
|
||||
t.start_time,
|
||||
t.status AS tournament_status,
|
||||
t.max_participants,
|
||||
t.created_at,
|
||||
t.participant_type AS tournament_participant_type,
|
||||
|
||||
/* =======================
|
||||
Game Title
|
||||
======================= */
|
||||
gt.id AS game_title_id,
|
||||
gt.name AS game_title_name,
|
||||
gt.description AS game_title_description,
|
||||
gt.web_link AS game_title_web_link,
|
||||
gt.image_name AS game_title_image_name,
|
||||
|
||||
/* =======================
|
||||
Tournament Participant
|
||||
======================= */
|
||||
tp.id AS participant_id,
|
||||
tp.user_id,
|
||||
tp.team_id,
|
||||
tp.participant_type,
|
||||
tp.seed,
|
||||
tp.joined_at
|
||||
|
||||
FROM tournaments t
|
||||
JOIN game_titles gt
|
||||
ON gt.id = t.game_title_id
|
||||
|
||||
LEFT JOIN tournament_participants tp
|
||||
ON tp.tournament_id = t.id
|
||||
|
||||
ORDER BY
|
||||
t.id,
|
||||
tp.seed IS NULL,
|
||||
tp.seed;
|
||||
|
||||
"""
|
||||
)
|
||||
await conn.commit()
|
||||
except aiomysql.InterfaceError:
|
||||
pool_init_result = await self.init_db_pool()
|
||||
if not pool_init_result:
|
||||
raise NoDatabaseConnectionError
|
||||
return await self.get_all_tournaments()
|
||||
except Exception as e:
|
||||
logger.warning(f"Error getting tournaments: {e}")
|
||||
|
||||
tournaments = []
|
||||
current_tournament: Optional[Tournament] = None
|
||||
for row in await cursor.fetchall():
|
||||
if current_tournament is None or current_tournament.id != row["tournament_id"]:
|
||||
if current_tournament is not None:
|
||||
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(
|
||||
id_=row["tournament_id"],
|
||||
name=row["tournament_name"],
|
||||
description=row["tournament_description"],
|
||||
game_title=GameTitle(
|
||||
name=row["game_title_name"],
|
||||
description=row["game_title_description"],
|
||||
web_link=row["game_title_web_link"],
|
||||
image_name=row["game_title_image_name"]
|
||||
),
|
||||
format_=self._parse_tournament_format(row["tournament_format"]),
|
||||
start_time=row["start_time"],
|
||||
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 [],
|
||||
matches=None, # ToDo: Implement
|
||||
rounds=[], # ToDo: Implement
|
||||
max_participants=row["max_participants"],
|
||||
participant_type=participant_type
|
||||
)
|
||||
else:
|
||||
id_accessor = "user_id" if current_tournament.participant_type == ParticipantType.PLAYER else "team_id"
|
||||
current_tournament.add_participant(
|
||||
Participant(id_=row[id_accessor], participant_type=self._parse_participant_type(row["participant_type"]))
|
||||
)
|
||||
else:
|
||||
tournaments.append(current_tournament)
|
||||
|
||||
return tournaments
|
||||
|
||||
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 conn.cursor(aiomysql.Cursor) as cursor:
|
||||
try:
|
||||
await cursor.execute(
|
||||
f"INSERT INTO tournament_participants (tournament_id, {accessor}, participant_type) VALUES (%s, %s, %s);",
|
||||
(tournament.id, participant.id, participant.participant_type.name)
|
||||
)
|
||||
await conn.commit()
|
||||
except aiomysql.InterfaceError:
|
||||
pool_init_result = await self.init_db_pool()
|
||||
if not pool_init_result:
|
||||
raise NoDatabaseConnectionError
|
||||
return await self.add_participant_to_tournament(participant, tournament)
|
||||
except Exception as e:
|
||||
logger.warning(f"Error adding participant to tournament: {e}")
|
||||
|
||||
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 conn.cursor(aiomysql.Cursor) as cursor:
|
||||
try:
|
||||
await cursor.execute(
|
||||
f"DELETE FROM tournament_participants WHERE (tournament_id = %s AND {accessor} = %s);",
|
||||
(tournament.id, participant.id)
|
||||
)
|
||||
await conn.commit()
|
||||
except aiomysql.InterfaceError:
|
||||
pool_init_result = await self.init_db_pool()
|
||||
if not pool_init_result:
|
||||
raise NoDatabaseConnectionError
|
||||
return await self.remove_participant_from_tournament(participant, tournament)
|
||||
except Exception as e:
|
||||
logger.warning(f"Error removing participant from tournament: {e}")
|
||||
|
||||
async def get_teams(self) -> list[Team]:
|
||||
async with self._connection_pool.acquire() as conn:
|
||||
async with conn.cursor(aiomysql.Cursor) as cursor:
|
||||
query = """
|
||||
SELECT
|
||||
t.id AS team_id,
|
||||
t.name AS team_name,
|
||||
t.abbreviation AS team_abbr,
|
||||
t.join_password,
|
||||
t.created_at AS team_created_at,
|
||||
|
||||
tm.status AS team_status,
|
||||
tm.joined_at AS member_joined_at,
|
||||
|
||||
u.*
|
||||
|
||||
FROM teams t
|
||||
|
||||
LEFT JOIN team_members tm
|
||||
ON t.id = tm.team_id
|
||||
|
||||
LEFT JOIN users u
|
||||
ON tm.user_id = u.user_id
|
||||
|
||||
ORDER BY
|
||||
t.id,
|
||||
CASE tm.status
|
||||
WHEN 'LEADER' THEN 1
|
||||
WHEN 'OFFICER' THEN 2
|
||||
WHEN 'MEMBER' THEN 3
|
||||
ELSE 4
|
||||
END,
|
||||
u.user_name;
|
||||
"""
|
||||
try:
|
||||
await cursor.execute(query)
|
||||
await conn.commit()
|
||||
except aiomysql.InterfaceError:
|
||||
pool_init_result = await self.init_db_pool()
|
||||
if not pool_init_result:
|
||||
raise NoDatabaseConnectionError
|
||||
return await self.get_teams()
|
||||
except Exception as e:
|
||||
logger.warning(f"Error getting teams: {e}")
|
||||
return []
|
||||
|
||||
current_team: Optional[Team] = None
|
||||
all_teams = []
|
||||
|
||||
for row in await cursor.fetchall():
|
||||
if row[5] is None: # Teams without single member are ignored
|
||||
continue
|
||||
if current_team is None:
|
||||
user = self._map_db_result_to_user(row[7:])
|
||||
current_team = Team(id=row[0], name=row[1], abbreviation=row[2], join_password=row[3], members={user: TeamStatus.from_str(row[5])})
|
||||
elif current_team.id == row[0]: # Still same team
|
||||
current_team.members[self._map_db_result_to_user(row[7:])] = TeamStatus.from_str(row[5])
|
||||
else:
|
||||
all_teams.append(current_team)
|
||||
user = self._map_db_result_to_user(row[7:])
|
||||
current_team = Team(id=row[0], name=row[1], abbreviation=row[2], join_password=row[3], members={user: TeamStatus.from_str(row[5])})
|
||||
|
||||
if current_team is not None:
|
||||
all_teams.append(current_team)
|
||||
|
||||
return all_teams
|
||||
|
||||
async def get_team_by_id(self, team_id: int) -> Optional[Team]:
|
||||
async with self._connection_pool.acquire() as conn:
|
||||
async with conn.cursor(aiomysql.Cursor) as cursor:
|
||||
query = """
|
||||
SELECT
|
||||
t.id AS team_id,
|
||||
t.name AS team_name,
|
||||
t.abbreviation AS team_abbr,
|
||||
t.join_password,
|
||||
t.created_at AS team_created_at,
|
||||
|
||||
tm.status AS team_status,
|
||||
tm.joined_at AS member_joined_at,
|
||||
|
||||
u.*
|
||||
|
||||
FROM teams t
|
||||
|
||||
LEFT JOIN team_members tm
|
||||
ON t.id = tm.team_id
|
||||
|
||||
LEFT JOIN users u
|
||||
ON tm.user_id = u.user_id
|
||||
|
||||
WHERE t.id = %s
|
||||
|
||||
ORDER BY
|
||||
t.id,
|
||||
CASE tm.status
|
||||
WHEN 'LEADER' THEN 1
|
||||
WHEN 'OFFICER' THEN 2
|
||||
WHEN 'MEMBER' THEN 3
|
||||
ELSE 4
|
||||
END,
|
||||
u.user_name;
|
||||
"""
|
||||
try:
|
||||
await cursor.execute(query, (team_id, ))
|
||||
await conn.commit()
|
||||
except aiomysql.InterfaceError:
|
||||
pool_init_result = await self.init_db_pool()
|
||||
if not pool_init_result:
|
||||
raise NoDatabaseConnectionError
|
||||
return await self.get_team_by_id(team_id)
|
||||
except Exception as e:
|
||||
logger.warning(f"Error getting team: {e}")
|
||||
return None
|
||||
|
||||
team: Optional[Team] = None
|
||||
|
||||
for row in await cursor.fetchall():
|
||||
if team is None:
|
||||
user = self._map_db_result_to_user(row[7:])
|
||||
team = Team(id=row[0], name=row[1], abbreviation=row[2], join_password=row[3], members={user: TeamStatus.from_str(row[5])})
|
||||
elif team.id == row[0]:
|
||||
team.members[self._map_db_result_to_user(row[7:])] = TeamStatus.from_str(row[5])
|
||||
|
||||
return team
|
||||
|
||||
async def create_team(self, team_name: str, team_abbr: str, join_password: str, leader: User) -> Team:
|
||||
async with self._connection_pool.acquire() as conn:
|
||||
async with conn.cursor(aiomysql.Cursor) as cursor:
|
||||
try:
|
||||
await cursor.execute(
|
||||
"INSERT INTO teams (name, abbreviation, join_password) "
|
||||
"VALUES (%s, %s, %s)", (team_name, team_abbr, join_password)
|
||||
)
|
||||
await conn.commit()
|
||||
team_id = cursor.lastrowid
|
||||
await cursor.execute(
|
||||
"INSERT INTO team_members (team_id, user_id, status) VALUES (%s, %s, %s)",
|
||||
(team_id, leader.user_id, TeamStatus.LEADER.name)
|
||||
)
|
||||
await conn.commit()
|
||||
return await self.get_team_by_id(team_id)
|
||||
|
||||
except aiomysql.InterfaceError:
|
||||
pool_init_result = await self.init_db_pool()
|
||||
if not pool_init_result:
|
||||
raise NoDatabaseConnectionError
|
||||
return await self.create_team(team_name, team_abbr, join_password)
|
||||
except aiomysql.IntegrityError as e:
|
||||
logger.warning(f"Aborted duplication entry: {e}")
|
||||
raise DuplicationError
|
||||
|
||||
async def update_team(self, team: Team) -> Team:
|
||||
async with self._connection_pool.acquire() as conn:
|
||||
async with conn.cursor(aiomysql.Cursor) as cursor:
|
||||
try:
|
||||
await cursor.execute(
|
||||
"UPDATE teams SET name = %s, abbreviation = %s, join_password = %s WHERE (id = %s)",
|
||||
(team.name, team.abbreviation, team.join_password, team.id)
|
||||
)
|
||||
await conn.commit()
|
||||
return await self.get_team_by_id(team.id)
|
||||
except aiomysql.InterfaceError:
|
||||
pool_init_result = await self.init_db_pool()
|
||||
if not pool_init_result:
|
||||
raise NoDatabaseConnectionError
|
||||
return await self.update_team(team)
|
||||
except aiomysql.IntegrityError as e:
|
||||
logger.warning(f"Aborted duplication entry: {e}")
|
||||
raise DuplicationError
|
||||
|
||||
|
||||
async def add_member_to_team(self, team: Team, user: User, status: TeamStatus = TeamStatus.MEMBER) -> None:
|
||||
async with self._connection_pool.acquire() as conn:
|
||||
async with conn.cursor(aiomysql.Cursor) as cursor:
|
||||
try:
|
||||
await cursor.execute(
|
||||
"INSERT INTO team_members (team_id, user_id, status) VALUES (%s, %s, %s)",
|
||||
(team.id, user.user_id, status.name)
|
||||
)
|
||||
await conn.commit()
|
||||
except aiomysql.InterfaceError:
|
||||
pool_init_result = await self.init_db_pool()
|
||||
if not pool_init_result:
|
||||
raise NoDatabaseConnectionError
|
||||
return await self.add_member_to_team(team, user, status)
|
||||
except aiomysql.IntegrityError as e:
|
||||
logger.warning(f"Failed to add member {user.user_name} to team {team.name}: {e}")
|
||||
raise DuplicationError
|
||||
|
||||
|
||||
async def remove_user_from_team(self, team: Team, user: User) -> None:
|
||||
async with self._connection_pool.acquire() as conn:
|
||||
async with conn.cursor(aiomysql.Cursor) as cursor:
|
||||
try:
|
||||
await cursor.execute(
|
||||
"DELETE FROM team_members WHERE team_id = %s AND user_id = %s",
|
||||
(team.id, user.user_id)
|
||||
)
|
||||
await conn.commit()
|
||||
except aiomysql.InterfaceError:
|
||||
pool_init_result = await self.init_db_pool()
|
||||
if not pool_init_result:
|
||||
raise NoDatabaseConnectionError
|
||||
return await self.remove_user_from_team(team, user)
|
||||
|
||||
async def change_tournament_status(self, tournament_id: int, status: TournamentStatus) -> None:
|
||||
async with self._connection_pool.acquire() as conn:
|
||||
async with conn.cursor(aiomysql.Cursor) as cursor:
|
||||
try:
|
||||
await cursor.execute(
|
||||
"UPDATE tournaments SET status = %s WHERE (id = %s)",
|
||||
(status.name, tournament_id)
|
||||
)
|
||||
await conn.commit()
|
||||
except aiomysql.InterfaceError:
|
||||
pool_init_result = await self.init_db_pool()
|
||||
if not pool_init_result:
|
||||
raise NoDatabaseConnectionError
|
||||
return await self.change_tournament_status(tournament_id, status)
|
||||
@@ -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)
|
||||
@@ -1,12 +1,10 @@
|
||||
import logging
|
||||
from decimal import Decimal
|
||||
from email.message import EmailMessage
|
||||
from asyncio import sleep
|
||||
|
||||
import aiosmtplib
|
||||
|
||||
from src.ezgg_lan_manager.services.ConfigurationService import ConfigurationService
|
||||
from src.ezgg_lan_manager.types.User import User
|
||||
from src.ez_lan_manager.services.ConfigurationService import ConfigurationService
|
||||
|
||||
logger = logging.getLogger(__name__.split(".")[-1])
|
||||
|
||||
@@ -18,9 +16,6 @@ class MailingService:
|
||||
async def send_email(self, subject: str, body: str, receiver: str) -> None:
|
||||
if self._configuration_service.DEV_MODE_ACTIVE:
|
||||
logger.info(f"Skipped sending mail to {receiver} because demo mode is active.")
|
||||
logger.info(f"Subject: {subject}")
|
||||
logger.info(f"Receiver: {receiver}")
|
||||
logger.info(f"Body: {body}")
|
||||
await sleep(1)
|
||||
return
|
||||
|
||||
@@ -40,15 +35,3 @@ class MailingService:
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to send email: {e}")
|
||||
|
||||
def generate_account_balance_added_mail_body(self, user: User, added_balance: Decimal, total_balance: Decimal) -> str:
|
||||
return f"""
|
||||
Hallo {user.user_name},
|
||||
|
||||
deinem Account wurden {added_balance:.2f} € hinzugefügt. Dein neues Guthaben beträgt nun {total_balance:.2f} €.
|
||||
|
||||
Wenn du zu dieser Aufladung Fragen hast, stehen wir dir in unserem Discord Server oder per Mail an {self._configuration_service.get_lan_info().organizer_mail} zur Verfügung.
|
||||
|
||||
Liebe Grüße
|
||||
Dein {self._configuration_service.get_lan_info().name} Team
|
||||
"""
|
||||
@@ -2,8 +2,8 @@ import logging
|
||||
from datetime import date
|
||||
from typing import Optional
|
||||
|
||||
from src.ezgg_lan_manager.services.DatabaseService import DatabaseService
|
||||
from src.ezgg_lan_manager.types.News import News
|
||||
from src.ez_lan_manager.services.DatabaseService import DatabaseService
|
||||
from src.ez_lan_manager.types.News import News
|
||||
|
||||
logger = logging.getLogger(__name__.split(".")[-1])
|
||||
|
||||
@@ -2,10 +2,10 @@ import logging
|
||||
from typing import Optional
|
||||
|
||||
|
||||
from src.ezgg_lan_manager.services.DatabaseService import DatabaseService
|
||||
from src.ezgg_lan_manager.services.TicketingService import TicketingService
|
||||
from src.ezgg_lan_manager.types.ConfigurationTypes import LanInfo, SeatingConfiguration
|
||||
from src.ezgg_lan_manager.types.Seat import Seat
|
||||
from src.ez_lan_manager.services.DatabaseService import DatabaseService
|
||||
from src.ez_lan_manager.services.TicketingService import TicketingService
|
||||
from src.ez_lan_manager.types.ConfigurationTypes import LanInfo, SeatingConfiguration
|
||||
from src.ez_lan_manager.types.Seat import Seat
|
||||
|
||||
logger = logging.getLogger(__name__.split(".")[-1])
|
||||
|
||||
@@ -22,7 +22,8 @@ class SeatAlreadyTakenError(Exception):
|
||||
pass
|
||||
|
||||
class SeatingService:
|
||||
def __init__(self, lan_info: LanInfo, db_service: DatabaseService, ticketing_service: TicketingService) -> None:
|
||||
def __init__(self, seating_configuration: SeatingConfiguration, lan_info: LanInfo, db_service: DatabaseService, ticketing_service: TicketingService) -> None:
|
||||
self._seating_configuration = seating_configuration
|
||||
self._lan_info = lan_info
|
||||
self._db_service = db_service
|
||||
self._ticketing_service = ticketing_service
|
||||
@@ -1,10 +1,10 @@
|
||||
import logging
|
||||
from typing import Optional
|
||||
|
||||
from src.ezgg_lan_manager.services.AccountingService import AccountingService, InsufficientFundsError
|
||||
from src.ezgg_lan_manager.services.DatabaseService import DatabaseService
|
||||
from src.ezgg_lan_manager.types.ConfigurationTypes import TicketInfo
|
||||
from src.ezgg_lan_manager.types.Ticket import Ticket
|
||||
from src.ez_lan_manager.services.AccountingService import AccountingService, InsufficientFundsError
|
||||
from src.ez_lan_manager.services.DatabaseService import DatabaseService
|
||||
from src.ez_lan_manager.types.ConfigurationTypes import TicketInfo
|
||||
from src.ez_lan_manager.types.Ticket import Ticket
|
||||
|
||||
logger = logging.getLogger(__name__.split(".")[-1])
|
||||
|
||||
@@ -2,8 +2,8 @@ from hashlib import sha256
|
||||
from typing import Union, Optional
|
||||
from string import ascii_letters, digits
|
||||
|
||||
from src.ezgg_lan_manager.services.DatabaseService import DatabaseService
|
||||
from src.ezgg_lan_manager.types.User import User
|
||||
from src.ez_lan_manager.services.DatabaseService import DatabaseService
|
||||
from src.ez_lan_manager.types.User import User
|
||||
|
||||
|
||||
class NameNotAllowedError(Exception):
|
||||
@@ -47,8 +47,7 @@ class UserService:
|
||||
user_name = user_name.lower()
|
||||
|
||||
hashed_pw = sha256(password_clear_text.encode(encoding="utf-8")).hexdigest()
|
||||
created_user = await self._db_service.create_user(user_name, user_mail, hashed_pw)
|
||||
return created_user
|
||||
return await self._db_service.create_user(user_name, user_mail, hashed_pw)
|
||||
|
||||
async def update_user(self, user: User) -> User:
|
||||
disallowed_char = self._check_for_disallowed_char(user.user_name)
|
||||
@@ -59,12 +58,9 @@ class UserService:
|
||||
|
||||
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_password_hash = sha256(password_clear_text.encode(encoding="utf-8")).hexdigest()
|
||||
if not user:
|
||||
return False
|
||||
if user.user_fallback_password and user.user_fallback_password == user_password_hash:
|
||||
return True
|
||||
return user.user_password == user_password_hash
|
||||
return user.user_password == sha256(password_clear_text.encode(encoding="utf-8")).hexdigest()
|
||||
|
||||
|
||||
def _check_for_disallowed_char(self, name: str) -> Optional[str]:
|
||||
@@ -4,8 +4,8 @@ from decimal import Decimal
|
||||
from enum import StrEnum
|
||||
from typing import Optional, Iterable, Self
|
||||
|
||||
from src.ezgg_lan_manager.types.CateringMenuItem import CateringMenuItem, CateringMenuItemCategory
|
||||
from src.ezgg_lan_manager.types.User import User
|
||||
from src.ez_lan_manager.types.CateringMenuItem import CateringMenuItem, CateringMenuItemCategory
|
||||
from src.ez_lan_manager.types.User import User
|
||||
|
||||
CateringMenuItemsWithAmount = dict[CateringMenuItem, int]
|
||||
|
||||
@@ -48,10 +48,3 @@ class LanInfo:
|
||||
@dataclass(frozen=True)
|
||||
class SeatingConfiguration:
|
||||
seats: dict[str, str]
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ReceiptPrintingConfiguration:
|
||||
host: str
|
||||
port: int
|
||||
order_print_endpoint: str
|
||||
password: str
|
||||
@@ -2,7 +2,7 @@ from dataclasses import dataclass
|
||||
from datetime import date
|
||||
from typing import Optional
|
||||
|
||||
from src.ezgg_lan_manager.types.User import User
|
||||
from src.ez_lan_manager.types.User import User
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -1,7 +1,7 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional
|
||||
|
||||
from src.ezgg_lan_manager.types.User import User
|
||||
from src.ez_lan_manager.types.User import User
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -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()
|
||||
@@ -2,7 +2,7 @@ from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
from src.ezgg_lan_manager.types.User import User
|
||||
from src.ez_lan_manager.types.User import User
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -9,7 +9,6 @@ class User:
|
||||
user_name: str
|
||||
user_mail: str
|
||||
user_password: str
|
||||
user_fallback_password: Optional[str]
|
||||
user_first_name: Optional[str]
|
||||
user_last_name: Optional[str]
|
||||
user_birth_day: Optional[date]
|
||||
@@ -20,9 +19,4 @@ class User:
|
||||
last_updated_at: datetime
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash(self.user_id)
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, User):
|
||||
return NotImplemented
|
||||
return self.user_id == other.user_id
|
||||
return hash(f"{self.user_id}{self.user_name}{self.user_mail}")
|
||||
@@ -1,40 +0,0 @@
|
||||
import logging
|
||||
|
||||
from from_root import from_root
|
||||
|
||||
from src.ezgg_lan_manager.services import *
|
||||
from src.ezgg_lan_manager.services.AccountingService import AccountingService
|
||||
from src.ezgg_lan_manager.services.CateringService import CateringService
|
||||
from src.ezgg_lan_manager.services.ConfigurationService import ConfigurationService
|
||||
from src.ezgg_lan_manager.services.DatabaseService import DatabaseService
|
||||
from src.ezgg_lan_manager.services.LocalDataService import LocalDataService
|
||||
from src.ezgg_lan_manager.services.MailingService import MailingService
|
||||
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.SeatingService import SeatingService
|
||||
from src.ezgg_lan_manager.services.TeamService import TeamService
|
||||
from src.ezgg_lan_manager.services.TicketingService import TicketingService
|
||||
from src.ezgg_lan_manager.services.TournamentService import TournamentService
|
||||
from src.ezgg_lan_manager.services.UserService import UserService
|
||||
from src.ezgg_lan_manager.types import *
|
||||
|
||||
# Inits services in the correct order
|
||||
def init_services() -> tuple[AccountingService, CateringService, ConfigurationService, DatabaseService, MailingService, NewsService, SeatingService, TicketingService, UserService, LocalDataService, ReceiptPrintingService, TournamentService, TeamService]:
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
configuration_service = ConfigurationService(from_root("config.toml"))
|
||||
db_service = DatabaseService(configuration_service.get_database_configuration())
|
||||
user_service = UserService(db_service)
|
||||
accounting_service = AccountingService(db_service)
|
||||
news_service = NewsService(db_service)
|
||||
mailing_service = MailingService(configuration_service)
|
||||
ticketing_service = TicketingService(configuration_service.get_ticket_info(), db_service, accounting_service)
|
||||
seating_service = SeatingService(configuration_service.get_lan_info(), db_service, ticketing_service)
|
||||
receipt_printing_service = ReceiptPrintingService(seating_service, configuration_service.get_receipt_printing_configuration(), configuration_service.DEV_MODE_ACTIVE)
|
||||
catering_service = CateringService(db_service, accounting_service, user_service, receipt_printing_service)
|
||||
local_data_service = LocalDataService()
|
||||
tournament_service = TournamentService(db_service, user_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
|
||||
|
Before Width: | Height: | Size: 57 KiB |
|
Before Width: | Height: | Size: 76 KiB |
|
Before Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 6.2 KiB |
|
Before Width: | Height: | Size: 65 KiB |
|
Before Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 9.8 KiB |