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
Three public feeds: /feed/events.xml (upcoming events), /feed/news.xml (announcements), /feed/pej.xml (nuclear news). RSS 2.0 format with KIG custom fields (thumbnail, datawydarzenia). RSS discovery links added to base.html head. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
167 lines
5.7 KiB
Python
167 lines
5.7 KiB
Python
"""
|
|
RSS Feed Routes
|
|
===============
|
|
|
|
Public RSS feeds for KIG (Krajowa Izba Gospodarcza) integration.
|
|
Format: RSS 2.0 with custom KIG fields (thumbnail, datawydarzenia, dc:creator, content).
|
|
|
|
Feeds:
|
|
- /feed/events.xml — upcoming Norda events
|
|
- /feed/news.xml — published announcements
|
|
- /feed/pej.xml — approved PEJ/nuclear news
|
|
"""
|
|
|
|
import logging
|
|
from datetime import date, datetime
|
|
from xml.sax.saxutils import escape
|
|
|
|
from flask import Response, request
|
|
|
|
from . import bp
|
|
from database import SessionLocal, NordaEvent, Announcement, ZOPKNews
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
SITE_URL = 'https://nordabiznes.pl'
|
|
ORG_NAME = 'Izba Gospodarcza Norda Biznes'
|
|
|
|
|
|
def _rss_date(dt):
|
|
"""Format datetime to RFC 822 for RSS pubDate."""
|
|
if isinstance(dt, date) and not isinstance(dt, datetime):
|
|
dt = datetime(dt.year, dt.month, dt.day)
|
|
if dt is None:
|
|
return ''
|
|
return dt.strftime('%a, %d %b %Y %H:%M:%S +0100')
|
|
|
|
|
|
def _kig_date(dt):
|
|
"""Format date to DD/MM/YYYY for KIG <datawydarzenia> field."""
|
|
if dt is None:
|
|
return ''
|
|
return dt.strftime('%d/%m/%Y')
|
|
|
|
|
|
def _build_rss(title, description, link, items):
|
|
"""Build RSS 2.0 XML string with KIG custom fields."""
|
|
xml_parts = [
|
|
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
'<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/">',
|
|
'<channel>',
|
|
f'<title>{escape(title)}</title>',
|
|
f'<link>{escape(link)}</link>',
|
|
f'<description>{escape(description)}</description>',
|
|
f'<language>pl</language>',
|
|
f'<lastBuildDate>{_rss_date(datetime.now())}</lastBuildDate>',
|
|
]
|
|
|
|
for item in items:
|
|
xml_parts.append('<item>')
|
|
if item.get('thumbnail'):
|
|
xml_parts.append(f'<thumbnail>{escape(item["thumbnail"])}</thumbnail>')
|
|
xml_parts.append(f'<title>{escape(item["title"])}</title>')
|
|
xml_parts.append(f'<link>{escape(item["link"])}</link>')
|
|
xml_parts.append(f'<dc:creator>{escape(item.get("creator", ORG_NAME))}</dc:creator>')
|
|
xml_parts.append(f'<content>{escape(item.get("content", ""))}</content>')
|
|
if item.get('datawydarzenia'):
|
|
xml_parts.append(f'<datawydarzenia>{escape(item["datawydarzenia"])}</datawydarzenia>')
|
|
xml_parts.append(f'<pubDate>{_rss_date(item.get("pub_date"))}</pubDate>')
|
|
xml_parts.append(f'<guid>{escape(item["link"])}</guid>')
|
|
xml_parts.append('</item>')
|
|
|
|
xml_parts.append('</channel>')
|
|
xml_parts.append('</rss>')
|
|
|
|
return '\n'.join(xml_parts)
|
|
|
|
|
|
@bp.route('/feed/events.xml')
|
|
def feed_events():
|
|
"""RSS feed of upcoming Norda events for KIG aggregation."""
|
|
with SessionLocal() as db:
|
|
events = db.query(NordaEvent).filter(
|
|
NordaEvent.event_date >= date.today(),
|
|
NordaEvent.access_level.in_(['public', 'members_only']),
|
|
).order_by(NordaEvent.event_date.asc()).limit(20).all()
|
|
|
|
items = []
|
|
for e in events:
|
|
items.append({
|
|
'title': e.title,
|
|
'link': f'{SITE_URL}/kalendarz/{e.id}',
|
|
'thumbnail': e.image_url or '',
|
|
'creator': e.organizer_name or ORG_NAME,
|
|
'content': e.description or '',
|
|
'datawydarzenia': _kig_date(e.event_date),
|
|
'pub_date': e.created_at or e.event_date,
|
|
})
|
|
|
|
xml = _build_rss(
|
|
title=ORG_NAME,
|
|
description='Nadchodzące wydarzenia Izby Gospodarczej Norda Biznes',
|
|
link=f'{SITE_URL}/kalendarz/',
|
|
items=items,
|
|
)
|
|
return Response(xml, mimetype='application/rss+xml; charset=utf-8')
|
|
|
|
|
|
@bp.route('/feed/news.xml')
|
|
def feed_news():
|
|
"""RSS feed of published announcements for KIG aggregation."""
|
|
with SessionLocal() as db:
|
|
announcements = db.query(Announcement).filter(
|
|
Announcement.status == 'published',
|
|
).order_by(Announcement.published_at.desc()).limit(20).all()
|
|
|
|
now = datetime.now()
|
|
items = []
|
|
for a in announcements:
|
|
if a.expires_at and a.expires_at < now:
|
|
continue
|
|
items.append({
|
|
'title': a.title,
|
|
'link': f'{SITE_URL}/ogloszenia/{a.slug}',
|
|
'thumbnail': a.image_url or '',
|
|
'creator': ORG_NAME,
|
|
'content': a.excerpt or a.content or '',
|
|
'datawydarzenia': '',
|
|
'pub_date': a.published_at or a.created_at,
|
|
})
|
|
|
|
xml = _build_rss(
|
|
title=ORG_NAME,
|
|
description='Aktualności i ogłoszenia Izby Gospodarczej Norda Biznes',
|
|
link=f'{SITE_URL}/ogloszenia',
|
|
items=items,
|
|
)
|
|
return Response(xml, mimetype='application/rss+xml; charset=utf-8')
|
|
|
|
|
|
@bp.route('/feed/pej.xml')
|
|
def feed_pej():
|
|
"""RSS feed of approved PEJ/nuclear news for KIG aggregation."""
|
|
with SessionLocal() as db:
|
|
news = db.query(ZOPKNews).filter(
|
|
ZOPKNews.status.in_(['approved', 'auto_approved']),
|
|
).order_by(ZOPKNews.published_at.desc()).limit(20).all()
|
|
|
|
items = []
|
|
for n in news:
|
|
items.append({
|
|
'title': n.title,
|
|
'link': n.url or f'{SITE_URL}/pej/aktualnosci',
|
|
'thumbnail': n.image_url or '',
|
|
'creator': n.source_name or ORG_NAME,
|
|
'content': n.ai_summary or n.description or '',
|
|
'datawydarzenia': '',
|
|
'pub_date': n.published_at or n.created_at,
|
|
})
|
|
|
|
xml = _build_rss(
|
|
title=f'{ORG_NAME} — Polska Elektrownia Jądrowa',
|
|
description='Aktualności o Polskiej Elektrowni Jądrowej z portalu Norda Biznes',
|
|
link=f'{SITE_URL}/pej/aktualnosci',
|
|
items=items,
|
|
)
|
|
return Response(xml, mimetype='application/rss+xml; charset=utf-8')
|