+75
-4
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user