chore: sync public repository
CI / checks (push) Has been cancelled

This commit is contained in:
maddin
2026-03-22 15:36:47 +00:00
parent 6fbd1bb3c2
commit 847f20c9d7
16 changed files with 402 additions and 23 deletions
+75 -4
View File
@@ -145,6 +145,12 @@ SUPPORT_TICKET_RATE_LIMIT_WINDOW = timedelta(minutes=30)
SUPPORT_TICKET_RATE_LIMIT_MAX_PER_IP = 3
SUPPORT_TICKET_RATE_LIMIT_MAX_PER_EMAIL = 5
SUPPORT_TICKET_MIN_FORM_SECONDS = 3
THEME_PREFERENCE_AUTO = "auto"
THEME_PREFERENCE_DARK = "dark"
THEME_PREFERENCE_LIGHT = "light"
THEME_PREFERENCES = {THEME_PREFERENCE_AUTO, THEME_PREFERENCE_DARK, THEME_PREFERENCE_LIGHT}
THEME_COLOR_DARK = "#2c2d2f"
THEME_COLOR_LIGHT = "#f3f4f6"
DAY_STATUS_QUERY_LABELS = {
DAY_STATUS_QUERY_VACATION: "Urlaub",
SPECIAL_DAY_STATUS_HOLIDAY: "Feiertag",
@@ -400,6 +406,16 @@ def create_app(settings_override: Settings | None = None) -> FastAPI:
"app_title": settings.resolved_app_title,
"app_version": settings.app_version,
"today_date": date.today(),
"theme_preference": (
user.theme_preference
if user and user.theme_preference in THEME_PREFERENCES
else THEME_PREFERENCE_AUTO
),
"theme_color": (
THEME_COLOR_LIGHT
if user and user.theme_preference == THEME_PREFERENCE_LIGHT
else THEME_COLOR_DARK
),
}
context.update(extra)
return context
@@ -463,6 +479,7 @@ def create_app(settings_override: Settings | None = None) -> FastAPI:
"automatic_break_rules_enabled": user.automatic_break_rules_enabled,
"default_break_minutes": user.default_break_minutes,
"preferred_home_view": user.preferred_home_view,
"theme_preference": user.theme_preference,
"preferred_month_view_mode": user.preferred_month_view_mode,
"entry_mode": user.entry_mode,
"overtime_start_date": user.overtime_start_date.isoformat() if user.overtime_start_date else None,
@@ -1897,6 +1914,7 @@ def create_app(settings_override: Settings | None = None) -> FastAPI:
"settings": {
"weekly_target_minutes": user.weekly_target_minutes,
"preferred_home_view": user.preferred_home_view,
"theme_preference": user.theme_preference,
"preferred_month_view_mode": user.preferred_month_view_mode,
"entry_mode": user.entry_mode,
"working_days": sorted(get_user_working_days(user)),
@@ -2027,7 +2045,7 @@ def create_app(settings_override: Settings | None = None) -> FastAPI:
def render_legal_page(request: Request, *, db: Session, key: str, title: str, subtitle: str | None = None) -> HTMLResponse:
markdown_text = get_site_content_markdown(db, key)
html_content = render_safe_markdown(markdown_text)
html_content = render_safe_markdown(markdown_text, obfuscate_emails=True)
user = get_current_user(request, db)
return templates.TemplateResponse(
"pages/legal_page.html",
@@ -2041,6 +2059,14 @@ def create_app(settings_override: Settings | None = None) -> FastAPI:
),
)
def require_verified_ticket_user(request: Request, db: Session) -> User | RedirectResponse:
user = get_current_user(request, db)
if not user or not user.is_active:
return RedirectResponse(url="/login?msg=contact_requires_login", status_code=status.HTTP_303_SEE_OTHER)
if not user.email_verified:
return RedirectResponse(url="/verify-email/resend", status_code=status.HTTP_303_SEE_OTHER)
return user
@app.get("/impressum", response_class=HTMLResponse)
async def impressum_page(request: Request, db: Session = Depends(get_db)):
return render_legal_page(
@@ -2063,7 +2089,10 @@ def create_app(settings_override: Settings | None = None) -> FastAPI:
@app.get("/kontakt", response_class=HTMLResponse)
async def contact_form(request: Request, db: Session = Depends(get_db)):
user = get_current_user(request, db)
required_user = require_verified_ticket_user(request, db)
if isinstance(required_user, RedirectResponse):
return required_user
user = required_user
return templates.TemplateResponse(
"pages/contact.html",
build_context(
@@ -2096,10 +2125,13 @@ def create_app(settings_override: Settings | None = None) -> FastAPI:
db: Session = Depends(get_db),
):
verify_csrf(request, csrf_token)
user = get_current_user(request, db)
required_user = require_verified_ticket_user(request, db)
if isinstance(required_user, RedirectResponse):
return required_user
user = required_user
normalized_name = name.strip()
normalized_email = email.strip().lower()
normalized_email = user.email.strip().lower()
normalized_subject = subject.strip()
normalized_message = message.strip()
started_at_expected = request.session.get("contact_form_started_at")
@@ -2495,6 +2527,10 @@ def create_app(settings_override: Settings | None = None) -> FastAPI:
success_message = "Bitte bestätige zuerst deine E-Mail-Adresse über den Link in der E-Mail."
elif msg == "email_verified":
success_message = "E-Mail-Adresse bestätigt. Du kannst dich jetzt anmelden."
elif msg == "contact_requires_login":
error_message = "Für Kontaktanfragen musst du zuerst eingeloggt sein."
elif msg == "contact_requires_verification":
error_message = "Bitte bestätige zuerst deine E-Mail-Adresse, bevor du das Ticketsystem nutzt."
elif msg == "email_verification_send_failed":
error_message = (
"Konto wurde erstellt, aber die Bestätigungs-E-Mail konnte nicht versendet werden. "
@@ -4367,6 +4403,7 @@ def create_app(settings_override: Settings | None = None) -> FastAPI:
async def settings_update_preferences(
request: Request,
preferred_home_view: str = Form(...),
theme_preference: str = Form(default=""),
preferred_month_view_mode: str = Form(...),
entry_mode: str = Form(default=""),
csrf_token: str = Form(...),
@@ -4385,6 +4422,15 @@ def create_app(settings_override: Settings | None = None) -> FastAPI:
error="Ungueltige Standardansicht.",
status_code=status.HTTP_400_BAD_REQUEST,
)
selected_theme_preference = (theme_preference or user.theme_preference or THEME_PREFERENCE_AUTO).strip()
if selected_theme_preference not in THEME_PREFERENCES:
return render_settings_form(
request,
db=db,
user=user,
error="Ungueltiges Darstellungs-Theme.",
status_code=status.HTTP_400_BAD_REQUEST,
)
if preferred_month_view_mode not in {"flat", "weeks"}:
return render_settings_form(
request,
@@ -4403,6 +4449,7 @@ def create_app(settings_override: Settings | None = None) -> FastAPI:
)
user.preferred_home_view = preferred_home_view
user.theme_preference = selected_theme_preference
user.preferred_month_view_mode = preferred_month_view_mode
new_entry_mode = entry_mode or user.entry_mode
switched_to_auto_until_today = user.entry_mode != ENTRY_MODE_AUTO_UNTIL_TODAY and new_entry_mode == ENTRY_MODE_AUTO_UNTIL_TODAY
@@ -4529,6 +4576,7 @@ def create_app(settings_override: Settings | None = None) -> FastAPI:
async def settings_update_weekly_target(
request: Request,
weekly_target_hours: float = Form(...),
entry_mode: str = Form(default=""),
automatic_break_rules_enabled: str | None = Form(default=None),
default_break_minutes_value: str = Form(default="", alias="default_break_minutes"),
csrf_token: str = Form(...),
@@ -4549,6 +4597,15 @@ def create_app(settings_override: Settings | None = None) -> FastAPI:
status_code=status.HTTP_400_BAD_REQUEST,
)
if entry_mode and entry_mode not in {ENTRY_MODE_MANUAL, ENTRY_MODE_AUTO_UNTIL_TODAY}:
return render_settings_form(
request,
db=db,
user=user,
error="Ungueltiger Erfassungsmodus.",
status_code=status.HTTP_400_BAD_REQUEST,
)
break_rules_enabled = automatic_break_rules_enabled == "on"
default_break_minutes = user.default_break_minutes if break_rules_enabled else 0
if default_break_minutes_value.strip():
@@ -4580,9 +4637,23 @@ def create_app(settings_override: Settings | None = None) -> FastAPI:
new_target_minutes=new_target_minutes,
scope="all_weeks",
)
new_entry_mode = entry_mode or user.entry_mode
switched_to_auto_until_today = user.entry_mode != ENTRY_MODE_AUTO_UNTIL_TODAY and new_entry_mode == ENTRY_MODE_AUTO_UNTIL_TODAY
user.weekly_target_minutes = new_target_minutes
user.entry_mode = new_entry_mode
user.automatic_break_rules_enabled = break_rules_enabled
user.default_break_minutes = default_break_minutes
delete_future_auto_entries(db=db, user_id=user.id, after_date=date.today())
if switched_to_auto_until_today:
ensure_user_has_default_target_rule(db, user)
autofill_entries_for_range(
db=db,
user=user,
range_start=user.created_at.date(),
range_end=date.today(),
)
db.commit()
return RedirectResponse(url="/settings?msg=weekly_target_updated", status_code=status.HTTP_303_SEE_OTHER)