# ----------------------------------------------------------------------------- # Rent-Ka — Rental listings aggregator (Canada, outside Québec) # Author: Simon-Pierre Boucher — contact@spboucher.ai # seo.py : search-engine optimization — light SSR, programmatic pages, # robots.txt, sitemaps # # Principle: for every public route the server returns the SAME index.html as # the Vite build, but with a unique (title, description, canonical, # og:, JSON-LD) and the essential content as HTML INSIDE
. # Search engines see a full page without executing JavaScript; React, on # mount, replaces that content with the interactive application. # ----------------------------------------------------------------------------- from __future__ import annotations import html import json import math import re import statistics import time import unicodedata from datetime import date, datetime, timezone from pathlib import Path from xml.sax.saxutils import escape as xml_escape from fastapi import APIRouter, HTTPException from fastapi.responses import HTMLResponse, PlainTextResponse, Response from . import db router = APIRouter() ROOT = Path(__file__).resolve().parent.parent FRONTEND_DIST = ROOT / "frontend" / "dist" SOURCES_PATH = ROOT / "data" / "sources.json" BASE_URL = "https://www.rent-ka.com" SITE_NAME = "Rent-Ka" # monthly-rent plausibility bounds — out of bounds: price excluded from the # statistics and structured data (some sources publish $0) PRICE_MIN, PRICE_MAX = 195, 15000 PRICE_OK = f"price >= {PRICE_MIN} AND price <= {PRICE_MAX}" # inclusion threshold for a programmatic page in the sitemap MIN_LISTINGS_PAGE = 3 SITEMAP_CHUNK = 10000 PROVINCE_NAMES = { "ON": "Ontario", "BC": "British Columbia", "AB": "Alberta", "SK": "Saskatchewan", "MB": "Manitoba", "NB": "New Brunswick", "NS": "Nova Scotia", "PE": "Prince Edward Island", "NL": "Newfoundland and Labrador", "YT": "Yukon", "NT": "Northwest Territories", "NU": "Nunavut", } # --- Slugs ------------------------------------------------------------------- def slugify(text: str) -> str: """«Trois-Rivières» → trois-rivieres, «2 bedrooms» → 2-bedrooms, «5+ bedrooms» → 5-plus-bedrooms.""" t = text.replace("½", "-1-2").replace("+", "-plus") t = t.replace("œ", "oe").replace("Œ", "Oe").replace("æ", "ae").replace("Æ", "Ae") t = unicodedata.normalize("NFKD", t).encode("ascii", "ignore").decode() t = re.sub(r"[^a-z0-9]+", "-", t.lower()).strip("-") return t _maps_cache: dict = {"ts": 0.0, "cities": {}, "types": {}} def _slug_maps() -> tuple[dict[str, str], dict[str, str]]: """(slug→city, slug→unit type), rebuilt at most every 10 minutes.""" if time.time() - _maps_cache["ts"] > 600: con = db.connect() cities = [r["city"] for r in con.execute( "SELECT DISTINCT city FROM listings WHERE active=1 AND city<>''")] types = [r["unit_type"] for r in con.execute( "SELECT DISTINCT unit_type FROM listings WHERE active=1 AND unit_type<>''")] con.close() _maps_cache["cities"] = {slugify(c): c for c in sorted(cities)} _maps_cache["types"] = {slugify(t): t for t in sorted(types)} _maps_cache["ts"] = time.time() return _maps_cache["cities"], _maps_cache["types"] # --- Template (index.html from the Vite build) --------------------------------- _shell_cache: dict = {"mtime": 0.0, "html": ""} def _shell() -> str: f = FRONTEND_DIST / "index.html" mtime = f.stat().st_mtime if mtime != _shell_cache["mtime"]: _shell_cache["html"] = f.read_text(encoding="utf-8") _shell_cache["mtime"] = mtime return _shell_cache["html"] def _render(*, title: str, description: str, path: str, jsonld: list[dict] | None = None, body: str = "", og_image: str | None = None, status: int = 200) -> HTMLResponse: """Build index.html + unique head + HTML content inside #root.""" canonical = BASE_URL + path page = _shell() page = re.sub(r".*?", lambda _m: f"{html.escape(title)}", page, count=1, flags=re.S) page = re.sub(r']*/>', lambda _m: f'', page, count=1) # strip static og:image/twitter metas from the template (re-injected below) page = re.sub(r'\s*]*/>', "", page) extras = [ f'', f'', f'', f'', '', '', f'', f'', f'', '', f'', ] img = og_image or (BASE_URL + "/og.png") extras.append(f'') if not og_image: extras.append('') extras.append('') extras.append(f'') for obj in (jsonld or []): blob = json.dumps(obj, ensure_ascii=False).replace("{blob}') page = page.replace("", " " + "\n ".join(extras) + "\n", 1) if body: seo_div = ('
' + body + '

Rent-Ka — A Groupe KA service

' + "
") page = page.replace('
', '
' + seo_div, 1) return HTMLResponse(page, status_code=status, headers={"Cache-Control": "no-cache"}) def _e(t) -> str: return html.escape(str(t or "")) def _fmt_price(p) -> str: return f"${int(round(p)):,}" if p else "" def _price_ok(p) -> bool: return p is not None and PRICE_MIN <= p <= PRICE_MAX def _iso(ts) -> str: if not ts: return date.today().isoformat() return datetime.fromtimestamp(ts, tz=timezone.utc).date().isoformat() def _listing_li(r) -> str: """One listing in a server-rendered HTML list.""" label = r["title"] or r["address"] or r["uid"] bits = [b for b in (r["unit_type"], _fmt_price(r["price"]) + "/month" if _price_ok(r["price"]) else "", r["sector"] or r["city"]) if b] return (f'
  • {_e(label)}' f'{" — " + _e(" · ".join(bits)) if bits else ""}
  • ') def _not_found(message: str, path: str) -> HTMLResponse: """HTML 404: the React shell is served (the SPA shows its page), but the status code and server content clearly say «not found» to bots.""" return _render(title="Page not found | Rent-Ka", description="This page does not exist on Rent-Ka.", path=path, body=f"

    {_e(message)}

    " '

    Browse all rentals across Canada · ' 'Rentals by city

    ', status=404) def _breadcrumb(items: list[tuple[str, str]]) -> dict: return {"@context": "https://schema.org", "@type": "BreadcrumbList", "itemListElement": [ {"@type": "ListItem", "position": i + 1, "name": name, "item": BASE_URL + path} for i, (name, path) in enumerate(items)]} # --- Data ---------------------------------------------------------------------- def _city_stats(con, city: str) -> dict: n = con.execute("SELECT COUNT(*) c FROM listings WHERE active=1 AND city=?", (city,)).fetchone()["c"] prices = [r["price"] for r in con.execute( f"SELECT price FROM listings WHERE active=1 AND city=? AND {PRICE_OK}", (city,))] types = [dict(r) for r in con.execute( """SELECT unit_type, COUNT(*) n FROM listings WHERE active=1 AND city=? AND unit_type<>'' GROUP BY unit_type ORDER BY n DESC""", (city,))] for t in types: t["slug"] = slugify(t["unit_type"]) return {"n": n, "avg": round(statistics.mean(prices)) if prices else None, "med": round(statistics.median(prices)) if prices else None, "types": types} def _cities_rows(con, minimum: int = 1) -> list[dict]: rows = [dict(r) for r in con.execute( f"""SELECT city, province, COUNT(*) n, AVG(CASE WHEN {PRICE_OK} THEN price END) avg_price, MAX(updated_at) last FROM listings WHERE active=1 AND city<>'' GROUP BY city HAVING n>=? ORDER BY n DESC""", (minimum,))] for r in rows: r["slug"] = slugify(r["city"]) r["avg_price"] = round(r["avg_price"]) if r["avg_price"] else None return rows def _parse_row(r) -> dict: d = dict(r) for k in ("amenities", "images"): d[k] = json.loads(d.get(k) or "[]") d["details"] = json.loads(d.get("details") or "{}") return d # --- JSON API for the frontend city pages --------------------------------------- @router.get("/api/seo/cities") def api_cities(): con = db.connect() rows = _cities_rows(con) con.close() return {"cities": rows} @router.get("/api/seo/city/{slug}") def api_city(slug: str, type: str | None = None): cities, types = _slug_maps() city = cities.get(slug) if not city: raise HTTPException(404, "Unknown city") unit_type = None if type: unit_type = types.get(type) if not unit_type: raise HTTPException(404, "Unknown unit type") con = db.connect() stats = _city_stats(con, city) sql = "SELECT * FROM listings WHERE active=1 AND city=?" args: list = [city] if unit_type: sql += " AND unit_type=?" args.append(unit_type) listings = [_parse_row(r) for r in con.execute( sql + " ORDER BY updated_at DESC LIMIT 100", args)] neighbors = [v for v in _cities_rows(con, MIN_LISTINGS_PAGE) if v["city"] != city][:12] con.close() return {"city": city, "slug": slug, "unit_type": unit_type, **stats, "listings": listings, "neighbors": neighbors} # --- robots.txt & sitemaps ------------------------------------------------------- @router.get("/robots.txt", include_in_schema=False) def robots() -> PlainTextResponse: return PlainTextResponse( "User-agent: *\n" "Allow: /\n" "Disallow: /api/\n" "Disallow: /uploads/\n" "Disallow: /profile\n" "Disallow: /favorites\n" "Disallow: /manage\n" "Disallow: /welcome\n" "Disallow: /bot\n" "Disallow: /gateway/\n" f"\nSitemap: {BASE_URL}/sitemap.xml\n") def _xml(content: str) -> Response: return Response('\n' + content, media_type="application/xml", headers={"Cache-Control": "public, max-age=3600"}) def _urlset(urls: list[tuple[str, str | None]]) -> Response: rows = [] for loc, lastmod in urls: lm = f"{lastmod}" if lastmod else "" rows.append(f"{xml_escape(loc)}{lm}") return _xml('\n' + "\n".join(rows) + "\n") @router.get("/sitemap.xml", include_in_schema=False) def sitemap_index(): con = db.connect() total = con.execute("SELECT COUNT(*) c FROM listings WHERE active=1").fetchone()["c"] con.close() chunks = max(1, math.ceil(total / SITEMAP_CHUNK)) names = (["sitemap-pages.xml", "sitemap-cities.xml"] + [f"sitemap-listings-{i}.xml" for i in range(1, chunks + 1)]) today = date.today().isoformat() rows = "\n".join( f"{BASE_URL}/{n}{today}" for n in names) return _xml('\n' + rows + "\n") @router.get("/sitemap-pages.xml", include_in_schema=False) def sitemap_pages(): urls: list[tuple[str, str | None]] = [ (f"{BASE_URL}/", None), (f"{BASE_URL}/cities", None), (f"{BASE_URL}/stats", None), (f"{BASE_URL}/sources", None), (f"{BASE_URL}/privacy", None), (f"{BASE_URL}/terms", None)] con = db.connect() for r in con.execute( """SELECT source, MAX(updated_at) last FROM listings WHERE active=1 GROUP BY source"""): urls.append((f"{BASE_URL}/g/{r['source']}", _iso(r["last"]))) con.close() return _urlset(urls) @router.get("/sitemap-cities.xml", include_in_schema=False) def sitemap_cities(): con = db.connect() urls: list[tuple[str, str | None]] = [] for v in _cities_rows(con, MIN_LISTINGS_PAGE): urls.append((f"{BASE_URL}/city/{v['slug']}", _iso(v["last"]))) for r in con.execute( f"""SELECT city, unit_type, COUNT(*) n, MAX(updated_at) last FROM listings WHERE active=1 AND city<>'' AND unit_type<>'' GROUP BY city, unit_type HAVING n>=?""", (MIN_LISTINGS_PAGE,)): urls.append((f"{BASE_URL}/city/{slugify(r['city'])}/{slugify(r['unit_type'])}", _iso(r["last"]))) con.close() return _urlset(urls) @router.get("/sitemap-listings-{num}.xml", include_in_schema=False) def sitemap_listings(num: int): if num < 1: raise HTTPException(404) con = db.connect() rows = con.execute( """SELECT uid, updated_at FROM listings WHERE active=1 ORDER BY uid LIMIT ? OFFSET ?""", (SITEMAP_CHUNK, (num - 1) * SITEMAP_CHUNK)).fetchall() con.close() if not rows: raise HTTPException(404) return _urlset([(f"{BASE_URL}/listing/{r['uid']}", _iso(r["updated_at"])) for r in rows]) # --- SSR pages ------------------------------------------------------------------- @router.get("/", include_in_schema=False) def home_ssr(): con = db.connect() total = con.execute("SELECT COUNT(*) c FROM listings WHERE active=1").fetchone()["c"] nsources = con.execute( "SELECT COUNT(DISTINCT source) c FROM listings WHERE active=1").fetchone()["c"] cities = _cities_rows(con, MIN_LISTINGS_PAGE) provinces = [dict(r) for r in con.execute( """SELECT province, COUNT(*) n FROM listings WHERE active=1 AND published=1 AND province<>'' GROUP BY province ORDER BY n DESC""")] recents = con.execute( f"""SELECT uid, title, address, sector, city, unit_type, price FROM listings WHERE active=1 AND {PRICE_OK} ORDER BY first_seen DESC LIMIT 20""").fetchall() con.close() title = f"Rent-Ka — A Groupe KA service · {total:,} rentals across Canada" description = (f"{total:,} apartments and homes for rent across Canada " f"(outside Québec), aggregated from {nsources} property " "managers and always up to date: Toronto, Ottawa, Vancouver, " "Calgary, Halifax and more. Photos, prices, availability and " "a direct link to the original listing.") top = cities[:30] body = ( f"

    Apartments for rent across Canada — {total:,} live listings

    " + f"

    Rent-Ka aggregates the rentals published by {nsources} property " "managers across Canada (outside Québec): every listing with its " "photos, price, availability and a direct link to the manager's " "website.

    " + "

    Rentals by province

      " + "".join(f"
    • {_e(PROVINCE_NAMES.get(p['province'], p['province']))}" f" — {p['n']} listings
    • " for p in provinces) + "

    Rentals by city

    All cities ({len(cities)})

    ' + "

    Latest listings

      " + "".join(_listing_li(r) for r in recents) + "
    ") jsonld = [ {"@context": "https://schema.org", "@type": "WebSite", "name": SITE_NAME, "url": BASE_URL + "/", "description": description, "inLanguage": "en-CA"}, {"@context": "https://schema.org", "@type": "Organization", "name": "Rent-Ka (Groupe KA)", "url": BASE_URL + "/"}] return _render(title=title, description=description, path="/", jsonld=jsonld, body=body) @router.get("/cities", include_in_schema=False) def cities_ssr(): con = db.connect() cities = _cities_rows(con) con.close() total = sum(v["n"] for v in cities) title = f"Rentals by city across Canada ({len(cities)} cities) | Rent-Ka" description = (f"Every Canadian city where Rent-Ka tracks rentals: " f"{total:,} listings in {len(cities)} cities, with the " "average rent and the number of available apartments per city.") body = (f"

    Rentals by city — {len(cities)} cities across Canada

      " + "".join(f'
    • {_e(v["city"])}' + (f" ({_e(v['province'])})" if v.get("province") else "") + f' — {v["n"]} listings' + (f", average rent {_fmt_price(v['avg_price'])}" if v["avg_price"] else "") + "
    • " for v in cities) + "
    ") jsonld = [_breadcrumb([("Home", "/"), ("Cities", "/cities")]), {"@context": "https://schema.org", "@type": "ItemList", "name": "Rentals by city across Canada", "numberOfItems": len(cities), "itemListElement": [ {"@type": "ListItem", "position": i + 1, "name": f"Apartments for rent in {v['city']}", "url": f"{BASE_URL}/city/{v['slug']}"} for i, v in enumerate(cities[:100])]}] return _render(title=title, description=description, path="/cities", jsonld=jsonld, body=body) def _city_ssr(slug: str, type_slug: str | None = None): cities, types = _slug_maps() path = f"/city/{slug}" + (f"/{type_slug}" if type_slug else "") city = cities.get(slug) if not city: return _not_found("No rentals tracked for this city", path) unit_type = None if type_slug is not None: unit_type = types.get(type_slug) if not unit_type: return _not_found("Unknown unit type", path) con = db.connect() stats = _city_stats(con, city) sql = "SELECT * FROM listings WHERE active=1 AND city=?" args: list = [city] if unit_type: sql += " AND unit_type=?" args.append(unit_type) n = con.execute(f"SELECT COUNT(*) c FROM ({sql})", args).fetchone()["c"] prices = [r["price"] for r in con.execute( f"SELECT price FROM listings WHERE active=1 AND city=? AND unit_type=? AND {PRICE_OK}", (city, unit_type))] avg = round(statistics.mean(prices)) if prices else None med = round(statistics.median(prices)) if prices else None else: n, avg, med = stats["n"], stats["avg"], stats["med"] rows = con.execute(sql + " ORDER BY updated_at DESC LIMIT 100", args).fetchall() neighbors = [v for v in _cities_rows(con, MIN_LISTINGS_PAGE) if v["city"] != city][:12] con.close() if n == 0: return _not_found(f"No active listing in {city} for this type", path) what = f"{unit_type} rentals" if unit_type else "Apartments for rent" title = f"{what} in {city} — {n} listings | Rent-Ka" desc_stats = (f"average rent {_fmt_price(avg)}, median {_fmt_price(med)}" if avg and med else "") description = (f"{n} {what.lower()} in {city}" + (f" ({desc_stats})" if desc_stats else "") + ". Up-to-date listings from property managers, with photos, " "prices, availability and a direct link to the original listing.") body = [f"

    {_e(what)} in {_e(city)} — {n} listings

    "] if avg and med: body.append(f"

    Average rent: {_fmt_price(avg)}/month · " f"median rent: {_fmt_price(med)}/month.

    ") if not unit_type and stats["types"]: body.append("

    By unit type

    ") body.append("

    Listings

      " + "".join(_listing_li(r) for r in rows) + "
    ") if unit_type: body.append(f'

    All rentals in {_e(city)}

    ') body.append("

    Other cities

    ") crumbs = [("Home", "/"), ("Cities", "/cities"), (city, f"/city/{slug}")] if unit_type: crumbs.append((f"{unit_type} in {city}", path)) jsonld = [_breadcrumb(crumbs), {"@context": "https://schema.org", "@type": "ItemList", "name": f"{what} in {city}", "numberOfItems": n, "itemListElement": [ {"@type": "ListItem", "position": i + 1, "name": r["title"] or r["address"] or r["uid"], "url": f"{BASE_URL}/listing/{r['uid']}"} for i, r in enumerate(rows[:50])]}] return _render(title=title, description=description, path=path, jsonld=jsonld, body="".join(body)) @router.get("/city/{slug}", include_in_schema=False) def city_ssr(slug: str): return _city_ssr(slug) @router.get("/city/{slug}/{type_slug}", include_in_schema=False) def city_type_ssr(slug: str, type_slug: str): return _city_ssr(slug, type_slug) @router.get("/listing/{uid:path}", include_in_schema=False) def listing_ssr(uid: str): con = db.connect() row = con.execute("SELECT * FROM listings WHERE uid=?", (uid,)).fetchone() con.close() if row is None: return _render(title="Listing not found | Rent-Ka", description="This listing does not or no longer exists on Rent-Ka.", path=f"/listing/{uid}", body="

    Listing not found

    " '

    Browse all rentals across Canada

    ', status=404) d = _parse_row(row) city_slug = slugify(d["city"]) if d["city"] else "" if not d["active"]: # listing removed at the source: 410 Gone + link to the parent city link = (f'Apartments for rent in {_e(d["city"])}' if city_slug else 'All rentals') return _render( title="Listing removed | Rent-Ka", description="This listing was removed by the property manager.", path=f"/listing/{uid}", body=f"

    This listing is no longer available

    " f"

    It was removed by the manager. {link}.

    ", status=410) label = d["title"] or d["address"] or "Rental unit" where = d["city"] if d["city"] and d["city"] not in label else "" title = (f"{d['unit_type'] + ' for rent — ' if d['unit_type'] else ''}{label}" + (f", {where}" if where else "") + " | Rent-Ka") price_txt = f"{_fmt_price(d['price'])}/month" if _price_ok(d["price"]) else (d["price_label"] or "") bits = [b for b in (d["unit_type"], price_txt, d["sector"], d["city"], d["availability"]) if b] description = (" · ".join(bits) + ". " if bits else "") + \ (d["description"][:150].strip() + "…" if len(d["description"] or "") > 150 else (d["description"] or "")).strip() description = description[:300] or f"Rental listing in {d['city']} on Rent-Ka." body = [f"

    {_e(label)}{' — ' + _e(d['unit_type']) if d['unit_type'] else ''}

    "] facts = [("Address", d["address"]), ("City", d["city"]), ("Neighbourhood", d["sector"]), ("Type", d["unit_type"]), ("Rent", price_txt), ("Availability", d["availability"]), ("Area", f"{int(d['area_sqft'])} sq ft" if d["area_sqft"] else "")] body.append("
      " + "".join(f"
    • {k}: {_e(v)}
    • " for k, v in facts if v) + "
    ") if d["description"]: body.append(f"

    {_e(d['description'][:600])}

    ") if d["amenities"]: body.append("

    Amenities: " + _e(", ".join(d["amenities"][:15])) + "

    ") if d["url"]: body.append(f'

    ' "See the original listing at the manager's site

    ") if city_slug: body.append(f'

    Other rentals in ' f'{_e(d["city"])}

    ') beds = None m = re.match(r"(\d+)", d["unit_type"] or "") if m: beds = int(m.group(1)) apartment: dict = { "@type": "Apartment", "name": label, "address": {"@type": "PostalAddress", "streetAddress": d["address"] or None, "addressLocality": d["city"] or None, "addressRegion": d.get("province") or "ON", "addressCountry": "CA"}} if d["lat"] and d["lng"]: apartment["geo"] = {"@type": "GeoCoordinates", "latitude": d["lat"], "longitude": d["lng"]} if beds: apartment["numberOfBedrooms"] = beds if d["area_sqft"]: apartment["floorSize"] = {"@type": "QuantitativeValue", "value": d["area_sqft"], "unitCode": "FTK"} if d["images"]: apartment["photo"] = d["images"][:5] listing_ld: dict = { "@context": "https://schema.org", "@type": "RealEstateListing", "name": title.removesuffix(" | Rent-Ka"), "url": f"{BASE_URL}/listing/{uid}", "datePosted": _iso(d["first_seen"]), "inLanguage": "en-CA", "about": apartment} if _price_ok(d["price"]): listing_ld["offers"] = { "@type": "Offer", "price": d["price"], "priceCurrency": "CAD", "availability": "https://schema.org/InStock", "businessFunction": "http://purl.org/goodrelations/v1#LeaseOut"} crumbs = [("Home", "/")] if city_slug: crumbs.append((d["city"], f"/city/{city_slug}")) crumbs.append((label, f"/listing/{uid}")) return _render(title=title, description=description, path=f"/listing/{uid}", jsonld=[listing_ld, _breadcrumb(crumbs)], body="".join(body), og_image=d["images"][0] if d["images"] else None) @router.get("/g/{source_id}", include_in_schema=False) def manager_ssr(source_id: str): registry = {s["id"]: s for s in json.loads(SOURCES_PATH.read_text(encoding="utf-8"))["sources"]} src = registry.get(source_id) if not src: return _not_found("Unknown manager", f"/g/{source_id}") con = db.connect() n = con.execute("SELECT COUNT(*) c FROM listings WHERE active=1 AND source=?", (source_id,)).fetchone()["c"] rows = con.execute( """SELECT uid, title, address, sector, city, unit_type, price FROM listings WHERE active=1 AND source=? ORDER BY updated_at DESC LIMIT 60""", (source_id,)).fetchall() con.close() name = src["name"] title = f"{name} — {n} rentals | Rent-Ka" description = (f"The {n} rentals from {name}" + (f" ({src['region']})" if src.get("region") else "") + " tracked by Rent-Ka, with prices, photos and a direct " "link to the original listing.") body = (f"

    {_e(name)} — {n} rentals

    " + (f"

    Areas: {_e(src['sectors'])}.

    " if src.get("sectors") else "") + "
      " + "".join(_listing_li(r) for r in rows) + "
    " + '

    All property managers

    ') jsonld = [_breadcrumb([("Home", "/"), ("Sources", "/sources"), (name, f"/g/{source_id}")]), {"@context": "https://schema.org", "@type": "Organization", "name": name, "url": src.get("url") or f"{BASE_URL}/g/{source_id}"}] return _render(title=title, description=description, path=f"/g/{source_id}", jsonld=jsonld, body=body) # static app pages: unique head, content rendered by React _STATIC_META = { "/stats": ("Canadian rental market statistics | Rent-Ka", "Average and median rents, breakdowns by city and unit type: " "the Canadian rental market statistics, computed continuously " "by Rent-Ka."), "/sources": ("Aggregated sources — property managers and portals | Rent-Ka", "Every source aggregated by Rent-Ka: Canadian property " "managers and rental portals, with each one's number of " "active listings."), "/privacy": ("Privacy policy | Rent-Ka", "Rent-Ka's privacy policy: collected data, cookies and " "user rights."), "/terms": ("Terms of use | Rent-Ka", "Terms of use of the Rent-Ka service, an independent rental " "listings aggregator covering Canada outside Québec."), } def _static_page(path: str): title, description = _STATIC_META[path] return _render(title=title, description=description, path=path) @router.get("/stats", include_in_schema=False) def stats_page(): return _static_page("/stats") @router.get("/sources", include_in_schema=False) def sources_page(): return _static_page("/sources") @router.get("/privacy", include_in_schema=False) def privacy_page(): return _static_page("/privacy") @router.get("/terms", include_in_schema=False) def terms_page(): return _static_page("/terms")