diff --git a/backend/src/tools/koepi/routes.js b/backend/src/tools/koepi/routes.js index 3bc1b48..94bb469 100644 --- a/backend/src/tools/koepi/routes.js +++ b/backend/src/tools/koepi/routes.js @@ -98,23 +98,28 @@ async function scrapeOffersWithPuppeteer() { const productImg = imgs[0]?.src || null; const publisherImg = imgs[1]?.src || null; - // Text parsen: "neuKönig Pilsener bei Trinkgut"Aktuelle Angebote", Seite 6Mo. 6.7. - Sa. 11.7.2026..." - const storeMatch = text.match(/bei\s+(.+?)\s*[""„]/); - const titleMatch = text.match(/[""„](.+?)[""„]/); - const pageMatch = text.match(/Seite\s+(\d+)/); - const dateMatch = text.match(/(\w+\.\s+\d+\.\d+\.)\s*-\s*(\w+\.\s+\d+\.\d+\.\d+)/); - const validMatch = text.match(/(Noch \d+ Tage? gültig|Gültig bis \S+)/); - const isNew = text.startsWith('neu'); + // Text parsen + const storeMatch = text.match(/bei\s+([^""„]+?)\s*[""„]/); + const titleMatch = text.match(/[""„]([^""„]+?)[""„]/); + const pageMatch = text.match(/Seite\s+(\d+)/); + const validMatch = text.match(/(Noch \d+ Tag[e]? g.ltig|G.ltig bis [\d\.]+)/); + const dateMatch = text.match(/(\d{1,2}\.\d{1,2}\.(?:\d{4})?)\s*-\s*(\w+\.?\s+\d{1,2}\.\d{1,2}\.\d{4})/); + const isNew = text.startsWith('neu'); + + // URL aus Link holen + const link = b.querySelector('a'); + const url = link?.href || null; return { - type: 'brochure', - store: storeMatch?.[1]?.trim() || null, - title: titleMatch?.[1]?.trim() || null, - page: pageMatch?.[1] ? parseInt(pageMatch[1]) : null, - dateRange: dateMatch ? `${dateMatch[1]} - ${dateMatch[2]}` : null, - validity: validMatch?.[1] || null, + type: 'brochure', + store: storeMatch?.[1]?.trim() || null, + title: titleMatch?.[1]?.trim() || null, + page: pageMatch?.[1] ? parseInt(pageMatch[1]) : null, + dateRange: dateMatch ? `${dateMatch[1]} - ${dateMatch[2]}` : null, + validity: validMatch?.[1] || null, isNew, - productImage: productImg, + url, + productImage: productImg, publisherLogo: publisherImg, }; }); @@ -256,4 +261,19 @@ router.post('/clear-cache', authenticate, (req, res) => { res.json({ ok: true }); }); +// ── Bild-Proxy (umgeht CSP) ────────────────────────────────────────────────── +router.get('/img', authenticate, (req, res) => { + const url = req.query.url; + if (!url || !url.startsWith('https://')) return res.status(400).end(); + const parsed = new URL(url); + const allowed = ['content-media.bonial.biz','publisher-media.bonial.biz','web-assets.kaufda.de']; + if (!allowed.some(h => parsed.hostname === h)) return res.status(403).end(); + const reqImg = https.get(url, { headers: { 'User-Agent': 'Mozilla/5.0' } }, (imgRes) => { + res.setHeader('Content-Type', imgRes.headers['content-type'] || 'image/jpeg'); + res.setHeader('Cache-Control', 'public, max-age=3600'); + imgRes.pipe(res); + }); + reqImg.on('error', () => res.status(502).end()); +}); + module.exports = router; diff --git a/frontend/src/tools/koepi.jsx b/frontend/src/tools/koepi.jsx index 85ee636..0420537 100644 --- a/frontend/src/tools/koepi.jsx +++ b/frontend/src/tools/koepi.jsx @@ -33,16 +33,21 @@ function publisherColor(name) { // ── Prospekt-Angebot Karte ──────────────────────────────────────────────────── function BrochureOfferCard({ offer }) { const color = publisherColor(offer.store); + const Wrapper = offer.url + ? ({children}) => {children} + : ({children}) =>
{children}
; return ( +
{/* Prospektseite als Bild */} {offer.productImage && (
- {`König { e.target.style.display='none'; }} /> @@ -80,19 +85,20 @@ function BrochureOfferCard({ offer }) {
)} {/* Gültigkeit */} - {offer.validity && ( -
- 🕐 {offer.validity} + {offer.dateRange && ( +
+ 📅 {offer.dateRange}
)} - {offer.dateRange && ( -
- {offer.dateRange} + {offer.validity && ( +
+ ⏰ {offer.validity}
)}
+ ); } @@ -108,7 +114,8 @@ function GridOfferCard({ offer }) { }}> {offer.image && (
- {offer.name} { e.target.parentElement.style.display='none'; }} />