Some checks are pending
NordaBiz Tests / Unit & Integration Tests (push) Waiting to run
NordaBiz Tests / E2E Tests (Playwright) (push) Blocked by required conditions
NordaBiz Tests / Smoke Tests (Production) (push) Blocked by required conditions
NordaBiz Tests / Send Failure Notification (push) Blocked by required conditions
- Add year range validation (2020-2100) on /kalendarz/ to prevent ValueError crash - Exempt notification/message unread-count endpoints from rate limiting (shared IP via NAT) - Replace deprecated google.generativeai SDK with google-genai in nordabiz_chat.py - Remove dead news_service import that logged warnings on every worker startup Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
203 lines
6.5 KiB
Python
203 lines
6.5 KiB
Python
"""
|
|
Calendar Routes
|
|
===============
|
|
|
|
Public calendar and event registration endpoints.
|
|
"""
|
|
|
|
from datetime import date
|
|
import calendar as cal_module
|
|
from flask import render_template, request, redirect, url_for, flash, jsonify
|
|
from flask_login import login_required, current_user
|
|
|
|
from . import bp
|
|
from database import SessionLocal, NordaEvent, EventAttendee
|
|
|
|
|
|
# Polish month names
|
|
POLISH_MONTHS = {
|
|
1: 'Styczeń', 2: 'Luty', 3: 'Marzec', 4: 'Kwiecień',
|
|
5: 'Maj', 6: 'Czerwiec', 7: 'Lipiec', 8: 'Sierpień',
|
|
9: 'Wrzesień', 10: 'Październik', 11: 'Listopad', 12: 'Grudzień'
|
|
}
|
|
|
|
|
|
@bp.route('/', endpoint='calendar_index')
|
|
@login_required
|
|
def index():
|
|
"""Kalendarz wydarzeń Norda Biznes - widok listy lub siatki miesięcznej"""
|
|
db = SessionLocal()
|
|
try:
|
|
today = date.today()
|
|
|
|
# Parametry widoku
|
|
view_mode = request.args.get('view', 'list') # list lub grid
|
|
year = request.args.get('year', today.year, type=int)
|
|
month = request.args.get('month', today.month, type=int)
|
|
|
|
# Walidacja roku
|
|
if year < 2020 or year > 2100:
|
|
return redirect(url_for('.calendar_index'))
|
|
|
|
# Walidacja miesiąca
|
|
if month < 1:
|
|
month = 12
|
|
year -= 1
|
|
elif month > 12:
|
|
month = 1
|
|
year += 1
|
|
|
|
# Oblicz poprzedni/następny miesiąc
|
|
if month == 1:
|
|
prev_month, prev_year = 12, year - 1
|
|
else:
|
|
prev_month, prev_year = month - 1, year
|
|
|
|
if month == 12:
|
|
next_month, next_year = 1, year + 1
|
|
else:
|
|
next_month, next_year = month + 1, year
|
|
|
|
# Dane dla widoku siatki
|
|
month_days = []
|
|
events_by_day = {}
|
|
|
|
if view_mode == 'grid':
|
|
# Pobierz wydarzenia z danego miesiąca
|
|
first_day = date(year, month, 1)
|
|
last_day = date(year, month, cal_module.monthrange(year, month)[1])
|
|
all_events = db.query(NordaEvent).filter(
|
|
NordaEvent.event_date >= first_day,
|
|
NordaEvent.event_date <= last_day
|
|
).order_by(NordaEvent.event_date.asc()).all()
|
|
|
|
# Filtruj wydarzenia według uprawnień użytkownika
|
|
events = [e for e in all_events if e.can_user_view(current_user)]
|
|
|
|
# Przygotuj strukturę kalendarza (poniedziałek = 0)
|
|
cal = cal_module.Calendar(firstweekday=0)
|
|
month_days = cal.monthdayscalendar(year, month)
|
|
|
|
# Mapuj wydarzenia na dni
|
|
for event in events:
|
|
day = event.event_date.day
|
|
if day not in events_by_day:
|
|
events_by_day[day] = []
|
|
events_by_day[day].append(event)
|
|
|
|
# Dane dla widoku listy (zawsze potrzebne dla fallback)
|
|
all_upcoming = db.query(NordaEvent).filter(
|
|
NordaEvent.event_date >= today
|
|
).order_by(NordaEvent.event_date.asc()).all()
|
|
|
|
# Filtruj według uprawnień
|
|
upcoming = [e for e in all_upcoming if e.can_user_view(current_user)]
|
|
|
|
all_past = db.query(NordaEvent).filter(
|
|
NordaEvent.event_date < today
|
|
).order_by(NordaEvent.event_date.desc()).limit(10).all()
|
|
|
|
past = [e for e in all_past if e.can_user_view(current_user)][:5]
|
|
|
|
return render_template('calendar/index.html',
|
|
# Dane dla widoku listy
|
|
upcoming_events=upcoming,
|
|
past_events=past,
|
|
today=today,
|
|
# Dane dla widoku siatki
|
|
view_mode=view_mode,
|
|
year=year,
|
|
month=month,
|
|
month_name=POLISH_MONTHS.get(month, ''),
|
|
month_days=month_days,
|
|
events_by_day=events_by_day,
|
|
prev_month=prev_month,
|
|
prev_year=prev_year,
|
|
next_month=next_month,
|
|
next_year=next_year,
|
|
)
|
|
finally:
|
|
db.close()
|
|
|
|
|
|
@bp.route('/<int:event_id>', endpoint='calendar_event')
|
|
@login_required
|
|
def event(event_id):
|
|
"""Szczegóły wydarzenia"""
|
|
db = SessionLocal()
|
|
try:
|
|
event = db.query(NordaEvent).filter(NordaEvent.id == event_id).first()
|
|
if not event:
|
|
flash('Wydarzenie nie istnieje.', 'error')
|
|
return redirect(url_for('.calendar_index'))
|
|
|
|
# Sprawdź uprawnienia dostępu
|
|
if not event.can_user_view(current_user):
|
|
flash('Nie masz uprawnień do wyświetlenia tego wydarzenia.', 'error')
|
|
return redirect(url_for('.calendar_index'))
|
|
|
|
# Sprawdź czy użytkownik jest zapisany
|
|
user_attending = db.query(EventAttendee).filter(
|
|
EventAttendee.event_id == event_id,
|
|
EventAttendee.user_id == current_user.id
|
|
).first()
|
|
|
|
return render_template('calendar/event.html',
|
|
event=event,
|
|
user_attending=user_attending
|
|
)
|
|
finally:
|
|
db.close()
|
|
|
|
|
|
@bp.route('/<int:event_id>/rsvp', methods=['POST'], endpoint='calendar_rsvp')
|
|
@login_required
|
|
def rsvp(event_id):
|
|
"""Zapisz się / wypisz z wydarzenia"""
|
|
db = SessionLocal()
|
|
try:
|
|
event = db.query(NordaEvent).filter(NordaEvent.id == event_id).first()
|
|
if not event:
|
|
return jsonify({'success': False, 'error': 'Wydarzenie nie istnieje'}), 404
|
|
|
|
# Sprawdź uprawnienia dostępu
|
|
if not event.can_user_attend(current_user):
|
|
return jsonify({'success': False, 'error': 'Nie masz uprawnień do zapisania się na to wydarzenie'}), 403
|
|
|
|
# Sprawdź czy już zapisany
|
|
existing = db.query(EventAttendee).filter(
|
|
EventAttendee.event_id == event_id,
|
|
EventAttendee.user_id == current_user.id
|
|
).first()
|
|
|
|
if existing:
|
|
# Wypisz
|
|
db.delete(existing)
|
|
db.commit()
|
|
return jsonify({
|
|
'success': True,
|
|
'action': 'removed',
|
|
'message': 'Wypisano z wydarzenia',
|
|
'attendee_count': event.attendee_count
|
|
})
|
|
else:
|
|
# Zapisz
|
|
if event.max_attendees and event.attendee_count >= event.max_attendees:
|
|
return jsonify({'success': False, 'error': 'Brak wolnych miejsc'}), 400
|
|
|
|
attendee = EventAttendee(
|
|
event_id=event_id,
|
|
user_id=current_user.id,
|
|
status='confirmed'
|
|
)
|
|
db.add(attendee)
|
|
db.commit()
|
|
return jsonify({
|
|
'success': True,
|
|
'action': 'added',
|
|
'message': 'Zapisano na wydarzenie',
|
|
'attendee_count': event.attendee_count
|
|
})
|
|
finally:
|
|
db.close()
|