- Migration 064 fixes 12 records in company_websites table missing https://
- Added ensure_url filter to w.url in contact bar template as safety net
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Updated Facebook link from /nordabiznes to /profile.php?id=100057396041901
across all 4 locations (email templates, JSON-LD schema)
- Added Facebook link to site footer (Contact section)
- Added "Follow us on Facebook" to landing page CTA
- Redesigned upcoming events: side-by-side layout instead of stacked
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Previous logo-circle.png was 404, favicon-192 was wrong icon.
Generated logo-email.png from favicon.svg compass via sharp.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Shared _email_v3_wrap() helper: branded header with logo, full footer
with address/links. Updated: password reset, welcome, forum reply,
role notification. Action buttons grid layout in /admin/users.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Email: dark header with compass, company card, green checkmarks, Polish
date format, full footer with address, phone and tech support contact.
Actions: 4-column grid layout instead of vertical stack.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds envelope icon in AKCJE column that sends an email to the user
with their current company role and permissions summary.
Uses approved v3 email template with Norda Business branding.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Every email sent via send_email() now includes a BCC to the portal
administrator (MAIL_BCC env var, defaults to maciej.pienczyn@inpi.pl).
Recipients who are already in TO are automatically excluded from BCC.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix address from Hallera 18 to 12 Marca 238/5 in all JSON-LD, contact
section, and Google Maps embed
- Update geo coordinates for new address
- Broaden company descriptions: not just Wejherowo but also powiat
wejherowski, neighboring counties, and wojewodztwo pomorskie
- Update all meta descriptions, OG tags, hero text, and tile headers
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add Polish city name declensions to local keyword matcher
- Add openingHours string format alongside openingHoursSpecification
- Add Wejherowo to page title for city_in_title signal
- Add service+city keyword phrases in visible text (serwis, transport,
szkolenia, sklep, remonty, instalacje + Wejherowo/Rumia/Reda/Gdynia)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add email, image, priceRange, openingHoursSpecification to LocalBusiness JSON-LD
- Add Google Maps embed with address section on landing page
- Add local keywords (Wejherowo, Kaszuby) in visible text
- Add frame-src CSP directive for Google Maps iframe
- Responsive layout for map section on mobile
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
parsedate_to_datetime returns offset-aware datetime from Last-Modified
header, but datetime.now() is naive. Strip tzinfo before subtraction.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add missing SEO elements to improve audit score from 89 to 95+:
- Canonical URL and dynamic meta description blocks in base.html
- Open Graph tags (og:title, og:description, og:image, og:url, og:locale)
- JSON-LD structured data (Organization + WebSite schemas)
- robots.txt route with proper Disallow rules
- sitemap.xml route with homepage and release-notes
- LocalBusiness JSON-LD schema on landing page for Local SEO
- Last-Modified header for freshness signals
- Preload critical image for LCP optimization
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1. seo_analyzer.py: Consider aria-label, title, img AND svg as valid
link text (SVG icon links were falsely counted as "without text")
2. routes_portal_seo.py: Calculate overall_seo score using
SEOAuditor._calculate_overall_score() before saving to DB
(was always None because stream route bypasses audit_company())
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1. Extract HSTS, CSP, X-Frame-Options, X-Content-Type-Options from
HTTP response headers during portal SEO audit (were always None
because SEOAuditor doesn't check security headers natively)
2. Add aria-label to all social media and website icon links on
landing page tiles (300 of 317 links had no text content,
only SVG icons)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Use auto table layout with minimal font (10px) and padding (3px 4px)
so browser calculates natural column widths. Remove colgroup.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Reduce font size to 11px, padding to 4px 5px, use table-layout fixed
with colgroup for column width control. Score badges at 10px.
All ~35 columns should now fit on a full-width monitor.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Use container-full class to expand this page to full monitor width
so the history table with ~35 columns is visible without scrolling.
Only affects this specific page, not the rest of the portal.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Show all collected SEO data in history table organized by color-coded groups:
PageSpeed scores, Core Web Vitals, On-Page SEO checks, Security headers,
Content metrics, and composite scores. Dashboard includes score cards with
deltas, check grid for 17 boolean checks, content metrics grid, and
horizontally scrollable history table with ~35 columns.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
OnPageSEOResult uses nested objects (meta_tags.title, images.total_images,
structured_data.has_structured_data). TechnicalSEOResult uses robots_txt.exists,
sitemap.exists, canonical.has_canonical. Fixed all field access paths.
Extracted DB save logic to _save_audit_to_db() for clarity.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Audit now runs step-by-step with real-time progress via Server-Sent Events.
Each of 9 steps (fetch, on-page, technical, PageSpeed, local SEO, citations,
freshness, save) shows status with spinner, checkmark, or error icon.
Removed old POST form in favor of SSE-based streaming approach.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
SEOAuditor result dict contains datetime objects that can't be serialized
to JSONB. Added _make_json_safe() to recursively convert them.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Model had columns (overall_score, on_page_score, etc.) that didn't exist
in the migration. Updated model and templates to match the actual table.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The audit_owner_required decorator was never defined in utils/decorators.py,
causing ImportError that prevented the entire admin blueprint from loading.
Uses the same is_audit_owner() pattern as routes_audits.py and routes_social.py.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add /admin/portal-seo to run SEO audits on nordabiznes.pl
using the same SEOAuditor used for company websites.
Tracks results over time for before/after comparison.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Show company logos with website and social media links
to unauthenticated visitors below the existing landing
page content, improving local content indexability.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace native title with styled tooltip that appears after half-second
hover. Larger, darker, with arrow pointer for better readability.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Show social media cards, SEO PageSpeed scores, and GBP stats
directly in admin view. Add "Profil publiczny" link to header.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add WWW discovery, Social Media audit, and Logo fetch buttons.
Replace spinner with progress bar showing step descriptions.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Bulk discovery skips companies with any candidate (including rejected)
- Single discovery skips URLs from previously rejected domains
- Dashboard shows list of companies rejected by admin with note
that they won't be re-searched in bulk mode
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
In-memory _bulk_jobs dict was per-worker in gunicorn (4 workers),
causing poll requests to miss job state. Now uses /tmp JSON files
visible to all workers.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Previous regex only matched 3-3-2-2 format. New universal pattern
catches any 10-digit NIP with dashes/spaces in any position.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Increase candidate pool from 3 to 5. Stop evaluating once a
candidate matches NIP/REGON/KRS (100% certainty).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Root page often lacks NIP/REGON. Now scrapes /kontakt/, /contact,
/o-nas, /o-firmie to find strong verification signals. Stops early
when NIP/REGON/KRS found.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Strip paths from candidate URLs (e.g. /kontakt/, /about/) to always
save root domain. Deduplicates results pointing to same domain.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Users saw 0 candidates because page didn't refresh after bulk
discovery completed and modal was closed.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>