bin/server/for the Poland ranking./locations/krakow,/locations/wroclaw,/locations/warszawa,/locations/gdansk,/locations/poznan,/locations/szczecin,/locations/lodzfor city rankings./packagesfor package ecosystems./latest/packages/npm,/latest/packages/npm/top,/latest/packages/npm/downloads,/latest/packages/npm/dependents./healthz.
The HTML uses semantic sections, tables, canonical URLs, meta descriptions, and JSON-LD dataset metadata.
Public pages are intentionally cacheable by URL. Polish pages use the default
unprefixed routes, English pages use /en/..., and both variants expose
self-canonical and hreflang links. The locale cookie is only a redirect
preference and must not become the shared cache key for indexed pages.
Recommended edge rules:
- Cache anonymous
GETandHEADHTML for public ranking, profile, language, package, and badge routes. - Bypass shared cache whenever the request carries the signed session cookie
polish_open_source_rank.session. - Vary cached public HTML by path and query string, not by arbitrary cookies.
- Keep
/auth/*,/logout,/internal/*, and responses withCache-Control: privateorno-storeout of shared cache. - Rate-limit
/auth/*,/badges/*,/internal/*, and ranking-detail bursts before requests reach the Rack app. - Do not rate-limit normal search crawler access to indexed Polish and English
pages. If crawler-specific limits are needed, verify them at the edge with
reverse DNS instead of trusting
User-Agent.
Cloudflare cache rules should be ordered from the most specific bypasses to the public HTML rule:
- Bypass dynamic app paths.
- Cache badges.
- Cache static assets.
- Cache anonymous public HTML.
Use this expression for the dynamic bypass rule:
(http.host eq "polish-open-source.pl" and (
(http.request.method ne "GET" and http.request.method ne "HEAD") or
starts_with(http.request.uri.path, "/auth/") or
http.request.uri.path eq "/logout" or
starts_with(http.request.uri.path, "/internal/") or
http.request.uri.path eq "/healthz" or
http.cookie contains "polish_open_source_rank.session"
))Use this expression for the badge rule:
(http.host eq "polish-open-source.pl" and starts_with(http.request.uri.path, "/badges/"))Use this expression for static assets:
(http.host eq "polish-open-source.pl" and (
starts_with(http.request.uri.path, "/css/") or
starts_with(http.request.uri.path, "/js/") or
starts_with(http.request.uri.path, "/icons/")
))Use this expression for anonymous public HTML:
(http.host eq "polish-open-source.pl")
and (http.request.method eq "GET" or http.request.method eq "HEAD")
and http.request.uri.query eq ""
and not (http.cookie contains "polish_open_source_rank.session")
and not starts_with(http.request.uri.path, "/badges/")
and (
not (http.cookie contains "locale=")
or http.request.uri.path eq "/en"
or starts_with(http.request.uri.path, "/en/")
)
and (
http.request.uri.path eq "/"
or http.request.uri.path eq "/latest"
or http.request.uri.path eq "/organizations"
or http.request.uri.path eq "/about"
or http.request.uri.path eq "/editions"
or http.request.uri.path eq "/languages"
or http.request.uri.path eq "/packages"
or http.request.uri.path eq "/en"
or http.request.uri.path eq "/en/latest"
or http.request.uri.path eq "/en/organizations"
or http.request.uri.path eq "/en/about"
or http.request.uri.path eq "/en/editions"
or http.request.uri.path eq "/en/languages"
or http.request.uri.path eq "/en/packages"
or starts_with(http.request.uri.path, "/latest/")
or starts_with(http.request.uri.path, "/organizations/")
or starts_with(http.request.uri.path, "/users/")
or starts_with(http.request.uri.path, "/repositories/")
or starts_with(http.request.uri.path, "/organization-repositories/")
or starts_with(http.request.uri.path, "/languages/")
or starts_with(http.request.uri.path, "/packages/")
or starts_with(http.request.uri.path, "/editions/")
or starts_with(http.request.uri.path, "/20")
or starts_with(http.request.uri.path, "/en/latest/")
or starts_with(http.request.uri.path, "/en/organizations/")
or starts_with(http.request.uri.path, "/en/users/")
or starts_with(http.request.uri.path, "/en/repositories/")
or starts_with(http.request.uri.path, "/en/organization-repositories/")
or starts_with(http.request.uri.path, "/en/languages/")
or starts_with(http.request.uri.path, "/en/packages/")
or starts_with(http.request.uri.path, "/en/editions/")
or starts_with(http.request.uri.path, "/en/20")
)The HTML rule must exclude the signed session cookie. It should also cache
unprefixed pages only when the locale cookie is absent, because anonymous
requests with locale=en may need a redirect to the /en/... URL. Explicit
/en/... paths can be cached because the language is encoded in the path.
For the HTML rule, keep the edge and browser TTLs on origin headers. The app
marks public HTML as fresh for 60 seconds, allows 5 minutes of
stale-while-revalidate, and allows 24 hours of stale-if-error so Cloudflare can
serve the last cached anonymous page instead of surfacing origin 502, 503,
or 504 responses during jobs. Badges use the same 24-hour stale-if-error
window; stable negative public 404s use a 5-minute stale-if-error window.
Snapshot publish and rollback purge Cloudflare when CLOUDFLARE_ZONE_ID and
CLOUDFLARE_API_TOKEN are configured. The purge uses purge_everything because
monthly publications update rankings, profiles, language and package pages, and
badges together. Do not force a long edge TTL without keeping that purge
configured.
/internal/* routes are operational pages protected by application-owned Basic
Auth before the route handlers run. Production must configure
INTERNAL_BASIC_AUTH_USERNAME and INTERNAL_BASIC_AUTH_PASSWORD; the deploy
script rejects missing credentials. nginx still forwards only the expected
internal prefix and applies edge rate limits, but it does not own credentials.
The app marks internal responses as no-store and noindex, but indexing
headers are not access control.
Public views render external URLs through one presentation helper that only
allows browser-safe http and https URLs without embedded credentials. Source
API, registry, and manifest URLs can still be stored for matching and diagnostics
without every template re-implementing URL safety rules.
The Rack rate limiter remains in-process for the current production shape: one
web container behind nginx plus nginx edge rate limits. It only trusts
X-Real-IP or X-Forwarded-For when REMOTE_ADDR belongs to a trusted local or
private proxy address.
PolishOpenSourceRank::Web::App owns request flow only: locale redirects,
session-cookie deferral, cache helpers, route helpers, and the current request's
composition instance. Boot-time concerns live in PolishOpenSourceRank::Web::Boot
so middleware order, session cookie settings, helper registration, route
registration, and static view services stay out of request handlers.
The web composition root is still an outer-layer detail. It exposes context collaborators instead of individual use-case delegators:
publicationfor public rankings, profiles, badges, period resolution, and public cache revision decisions.packagesfor package index, ecosystem, and ranking-detail use cases.languagesfor language index and language ranking use cases.communityfor OAuth clients, Discord role sync, Discord panel state, and contributor access.operationsfor internal job progress.sitemap_catalogfor the public URL inventory used bysitemap.xml.developmentfor development-only login candidates.
Controllers should call those context collaborators directly, for example
publication.show_rankings or packages.show_package_index. New web use cases
should be added to the narrow owning context instead of adding pass-through
delegators to Web::App.
The composition boundary owns read-model construction. Request handlers must not
reach through to ranking_read_model, profile_read_model, or
package_ranking_read_model; when a route needs a higher-level view of public
data, add a semantic collaborator such as sitemap_catalog or development
that hides the read-model and metric details.
HTML cache revisions are calculated by PolishOpenSourceRank::Web::HtmlRevision.
It watches all ERB views, public CSS, public JavaScript, and the selected locale
file, so adding a new view automatically invalidates public HTML ETags without
updating a manual manifest.