""" Announcements Routes - Public blueprint Migrated from app.py as part of the blueprint refactoring. Contains public-facing announcement routes for logged-in members. """ import logging from datetime import datetime from flask import flash, redirect, render_template, request, url_for from flask_login import current_user, login_required from utils.decorators import member_required from sqlalchemy import desc, func, or_ from sqlalchemy.dialects.postgresql import array as pg_array from database import SessionLocal, Announcement, AnnouncementRead, User from . import bp logger = logging.getLogger(__name__) # ============================================================ # PUBLIC ANNOUNCEMENTS PAGE # ============================================================ @bp.route('/ogloszenia') @login_required @member_required def announcements_list(): """Strona z listą ogłoszeń dla zalogowanych członków""" db = SessionLocal() try: page = request.args.get('page', 1, type=int) category = request.args.get('category', '') per_page = 12 # Base query: published and not expired query = db.query(Announcement).filter( Announcement.status == 'published', or_( Announcement.expires_at.is_(None), Announcement.expires_at > datetime.now() ) ) # Filter by category (supports both single category and categories array) # Use PostgreSQL @> operator for array contains if category and category in Announcement.CATEGORIES: query = query.filter(Announcement.categories.op('@>')(pg_array([category]))) # Sort: pinned first, then by published_at desc query = query.order_by( desc(Announcement.is_pinned), desc(Announcement.published_at) ) # Pagination total = query.count() total_pages = (total + per_page - 1) // per_page announcements = query.offset((page - 1) * per_page).limit(per_page).all() return render_template('announcements/list.html', announcements=announcements, current_category=category, categories=Announcement.CATEGORIES, category_labels=Announcement.CATEGORY_LABELS, page=page, total_pages=total_pages, total=total) finally: db.close() @bp.route('/ogloszenia/') @login_required @member_required def announcement_detail(slug): """Szczegóły ogłoszenia dla zalogowanych członków""" db = SessionLocal() try: announcement = db.query(Announcement).filter( Announcement.slug == slug, Announcement.status == 'published', or_( Announcement.expires_at.is_(None), Announcement.expires_at > datetime.now() ) ).first() if not announcement: flash('Nie znaleziono ogłoszenia lub zostało usunięte.', 'error') return redirect(url_for('announcements_list')) # Increment views counter announcement.views_count = (announcement.views_count or 0) + 1 # Record read by current user (if not already recorded) existing_read = db.query(AnnouncementRead).filter( AnnouncementRead.announcement_id == announcement.id, AnnouncementRead.user_id == current_user.id ).first() if not existing_read: new_read = AnnouncementRead( announcement_id=announcement.id, user_id=current_user.id ) db.add(new_read) db.commit() # Get readers (users who read this announcement) readers = db.query(AnnouncementRead).filter( AnnouncementRead.announcement_id == announcement.id ).order_by(desc(AnnouncementRead.read_at)).all() # Get total registered users count for percentage calculation total_users = db.query(func.count(User.id)).filter( User.is_active == True, User.is_verified == True ).scalar() or 1 readers_count = len(readers) read_percentage = round((readers_count / total_users) * 100, 1) if total_users > 0 else 0 # Get other recent announcements for sidebar other_announcements = db.query(Announcement).filter( Announcement.status == 'published', Announcement.id != announcement.id, or_( Announcement.expires_at.is_(None), Announcement.expires_at > datetime.now() ) ).order_by(desc(Announcement.published_at)).limit(5).all() return render_template('announcements/detail.html', announcement=announcement, other_announcements=other_announcements, category_labels=Announcement.CATEGORY_LABELS, readers=readers, readers_count=readers_count, total_users=total_users, read_percentage=read_percentage) finally: db.close()