Impressums Seite. Datenschutzerklärung. Servitor Basic Test funktion.

This commit is contained in:
Daniel Nagel 2026-04-07 08:30:53 +00:00
parent b707dd96d6
commit b7f9e7a6bb
7 changed files with 222 additions and 9 deletions

15
bot/servitor.py Normal file
View File

@ -0,0 +1,15 @@
import requests
webhook_url = "https://liga-n8n.au-fab.eu/webhook/21066d30-2757-4d64-9c72-e439ecd70f94"
discord_tokken = "MTQ3NTg1ODU5OTQwNTQyNDcwMQ.GRo63W.1erAk0janBqS8NlHB6FHEbXM1bolIOPH2Uc4vs"
def send_message(event, text):
data = {
event: "Neuer Spieler",
text: "mr_teels"
}
response = requests.post(webhook_url, json=data)

124
gui/imprint.json Normal file
View File

@ -0,0 +1,124 @@
{
"version": 1,
"default_locale": "de",
"locales": {
"de": {
"page": {
"title": "Info"
},
"sections": [
{
"id": "rules",
"order": 10,
"title": "Allgemeine Regeln",
"blocks": [
{
"type": "markdown",
"content": [
"Mit der Anmeldung in der Liga stimmst du folgenden Richtlinien und Regeln zu:\n",
"* Freundliches und Respektvolles Verhalten gegenüber anderen Mitspielern.",
"* Kein Cheating / absichtliches Falschmelden in die Liga! Fehler können passieren.",
"* Es giltet ein Gentleman's Agreement: 'Wir schreiben keine absichtlichen Konterlisten. Schlecht geschriebene Regeln werden nicht zu unserem Vorteil ausgenutzt!'.",
"* Wir spielen mit **Intent**. Sprich: Es werden keine Regeln der eigenen Armee dem Gegner verheimlicht um Überraschungen zu produzieren."
]
}
]
},
{
"id": "imprint",
"order": 20,
"title": "Impressum",
"blocks": [
{
"type": "markdown",
"content": "**Verantwortlich:** Daniel Nagel (Privatperson) \n**Adresse:** PRIVAT \n**Kontakt:** admin@danielnagel.at"
}
]
},
{
"id": "privacy",
"order": 30,
"title": "Datenschutz",
"blocks": [
{
"type": "markdown",
"content": [
"Die Daten die für den Betrieb der Liga nötig sind werden auf einem privaten Server in Österreich gespeichert. Das bezieht ein: \n",
"* Spieler Namen (Automatisch generiert oder Selbst eingetragen)",
"* Spieler ID in form der Discord Nutzer ID.",
"* Discord Nutzernamen",
"* Discord url Pfad zum Profilfoto",
"\nDiese Daten werden **keinem** anderen Dienst, Service, Anbieter oder Drittem zur Verfügung gestellt!",
"\nEine Löschung der eigenen Daten kann jederzeit beantragt werden wenn man nicht mehr an der Liga teilnehmen will. \n",
"Bestimmte Ereignisse oder Eingaben in die App werden in eine Log Funktion geschrieben (mit Zeitstempel) für die Qualitätssicherung und störungsfreie Funktion der App. Unter anderem: \n",
"* Alle Eingaben vom Match Formular. Inkl. Spieler der es eingegeben hat.",
"* Wenn ein Match abgelehnt oder gelöscht wird. Inkl. Spieler der es gelöscht/abgelehnt hat.",
"* Genaue Aufschlüsselung der MMR Punkte Berechnung pro eingetragenem Match.",
"* Neu angelegte Spieler und ihren Discord Namen."
]
}
]
}
]
},
"en": {
"page": {
"title": "Info"
},
"sections": [
{
"id": "rules",
"order": 10,
"title": "General Rules",
"blocks": [
{
"type": "markdown",
"content": [
"By registering for the league you agree to the following guidelines and rules:\n",
"* Friendly and respectful behaviour towards all other players.",
"* No cheating or intentional misreporting of match results! Honest mistakes can happen.",
"* A Gentleman's Agreement applies: 'We do not write intentional counter-lists. Poorly written rules are not exploited for our own advantage!'.",
"* We play with **Intent**. This means: no rules of your own army are hidden from your opponent in order to create surprises."
]
}
]
},
{
"id": "imprint",
"order": 20,
"title": "Imprint",
"blocks": [
{
"type": "markdown",
"content": "**Responsible:** Daniel Nagel (Private individual) \n**Address:** PRIVATE \n**Contact:** admin@danielnagel.at"
}
]
},
{
"id": "privacy",
"order": 30,
"title": "Privacy Policy",
"blocks": [
{
"type": "markdown",
"content": [
"Data required to operate the league is stored on a private server located in Austria. This includes: \n",
"* Player names (automatically generated or manually entered).",
"* Player ID in the form of the Discord user ID.",
"* Discord usernames.",
"* Discord URL path to the profile picture.",
"\nThis data is **not** shared with any other service, provider, or third party.",
"\nDeletion of your own data can be requested at any time if you no longer wish to participate in the league. \n",
"Certain events or inputs within the app are written to a log function (with timestamp) to ensure quality and stable operation of the app. This includes: \n",
"* All inputs from the match form, including the player who submitted it.",
"* When a match is rejected or deleted, including the player who rejected/deleted it.",
"* Detailed breakdown of the MMR point calculation per submitted match.",
"* Newly created players and their Discord names."
]
}
]
}
]
}
}
}

75
gui/imprint_gui.py Normal file
View File

@ -0,0 +1,75 @@
import json
from pathlib import Path
from nicegui import ui
from fastapi import Request
from gui import gui_style
# JSON laden - liegt laut Screenshot direkt in gui/
_JSON_PATH = Path(__file__).resolve().parent / 'imprint.json'
with open(_JSON_PATH, encoding='utf-8') as f:
imprint_data: dict = json.load(f)
# -------------------------------------------------------
SECTION_TITLES_FALLBACK = {
"howto": "Kurzanleitung",
"rules": "Allgemeine Regeln",
"imprint": "Impressum",
"privacy": "Datenschutz",
}
def _get_locale(request: Request) -> str:
default = imprint_data.get('default_locale', 'de')
lang = request.query_params.get('lang', default)
if lang not in imprint_data.get('locales', {}):
lang = default
return lang
def _get_locale_options() -> list[str]:
return list(imprint_data.get('locales', {}).keys())
def setup_routes():
@ui.page('/info', dark=True)
def info_page(request: Request):
gui_style.apply_design()
lang = _get_locale(request)
locale_data = imprint_data['locales'][lang]
page_title = locale_data.get('page', {}).get('title', 'Info')
sections = sorted(locale_data.get('sections', []), key=lambda s: s.get('order', 0))
with ui.row().classes("w-full items-center justify-between"):
ui.button(text="Zurück", on_click=lambda: ui.navigate.to('/'))
ui.label(page_title).classes("text-xl font-bold")
ui.select(
options=_get_locale_options(),
value=lang,
label='Sprache',
on_change=lambda e: ui.navigate.to(f'/info?lang={e.value}'),
).classes("min-w-[8rem]")
ui.separator().classes("my-4")
with ui.column().classes("w-full gap-4"):
for section in sections:
title = section.get('title', SECTION_TITLES_FALLBACK.get(section.get('id', ''), ''))
blocks = section.get('blocks', [])
with ui.card().classes("w-full"):
if title:
ui.label(title).classes("text-lg font-semibold mb-2")
for block in blocks:
btype = block.get('type', 'markdown')
if btype == 'markdown':
raw = block.get('content', '')
# Unterstützt sowohl String als auch Array
if isinstance(raw, list):
content = '\n'.join(raw)
else:
content = raw
ui.markdown(content).classes("prose max-w-none")
elif btype == 'divider':
ui.separator().classes("my-2")

View File

@ -16,19 +16,17 @@ def setup_routes(admin_discord_id):
# Fehlt die Discord-ID (altes Cookie) ODER sagt die Datenbank, dass da was nicht stimmt?
if not discord_id or not data_api.validate_user_session(db_id, discord_id):
# Ausweis ungültig! Wir vernichten das Cookie sofort.
app.storage.user.clear()
ui.notify("Deine Sitzung ist ungültig oder abgelaufen. Bitte neu einloggen!", color="negative")
ui.navigate.reload()
return
# -----------------------------------------
# ---------------------------
# --- NAVIGATIONSLEISTE (HEADER) ---
# --- NAVIGATIONSLEISTE (HEADER)
# ---------------------------
with ui.header().classes('items-center justify-between bg-zinc-900 shadow-lg').props("reveal reveal-offset=1"):
with ui.header(fixed=False).classes('items-center justify-between bg-zinc-900 shadow-lg'):
# --- LINKE SEITE ---
# Vereinslogo und den Titel in einer eigenen Reihe (Reihe 1)
@ -291,3 +289,5 @@ def setup_routes(admin_discord_id):
else:
ui.label("Noch keine Spiele absolviert.").classes("text-infotext italic")
with ui.footer(fixed=False).classes('items-center justify-between bg-zinc-900 shadow-lg'):
ui.button(icon="description", on_click=lambda: ui.navigate.to('/info'))

View File

@ -5,10 +5,9 @@ from match_calculations import calc_match
from gui.info_text import info_system
def setup_routes():
# 1. Die {}-Klammern definieren eine dynamische Variable in der URL
@ui.page('/add-match/{system_name}', dark=True)
def match_form_page(system_name: str): # <--- WICHTIG: Hier fangen wir das Wort aus der URL auf!
def match_form_page(system_name: str): # <-- Hier wird der Name des Spielsystems gefiltert.
gui_style.apply_design()
# --- SICHERHEITS-CHECK ---

View File

@ -3,7 +3,7 @@ from dotenv import load_dotenv
from nicegui import ui, app
from data import database
from gui import main_gui, match_gui, discord_login, league_statistic, admin_gui, match_history_gui
from gui import main_gui, match_gui, discord_login, league_statistic_gui, admin_gui, match_history_gui, imprint_gui
from wood import logger
from gui.info_text import info_system
@ -30,11 +30,11 @@ logger.setup_log_db()
# 3. Seitenrouten aufbauen
main_gui.setup_routes(admin_discord_id)
discord_login.setup_login_routes()
league_statistic.setup_routes()
league_statistic_gui.setup_routes()
match_gui.setup_routes()
admin_gui.setup_routes()
match_history_gui.setup_routes()
imprint_gui.setup_routes()
# 4. Wir starten die NiceGUI App
ui.run(title="Westside Diceghost Liga", port=9000, storage_secret="EIN_super-geheimes_Pa$$wort#!", favicon="gui/pictures/wsdg.png")