BusyBox 1.37 nc -z is broken (outputs "punt!" and never exits),
causing the wait-for-cache init container to loop indefinitely.
The cache is healthy — the backend should handle reconnects itself.
Prevents the backend from starting before the DragonflyDB operator
has the cache pod ready and reachable. Mirrors the existing
wait-for-postgres pattern in the migration job.
Replace manual Valkey Deployment+Service with DragonflyDB operator CRD.
Add sectionName to HTTPRoute for HTTPS listener pinning and a separate
HTTP→HTTPS 301 redirect route. Update resources from req=limit to
request/limit separation for pay-as-you-go billing. Fix NetworkPolicy
cache pod selector to match operator-managed labels.
Mistral rejects json response format when tools (web search) are
active. Override to text since the prompt already requests JSON
output in the instructions.
Use the Conversations API (POST /v1/conversations) via the SDK for
Pass 1 agent calls instead of /agents/completions which doesn't
support built-in web search connectors. Pass 2 uses the SDK's
ChatComplete method.
Add Woodpecker secrets for AI_API_KEY, AI_AGENT_SIMPLE, and
TURNSTILE_SECRET_KEY. Create ci-secrets.yaml template and wire
them through the deploy step alongside existing SMTP secrets.
Set CPU and memory requests equal to limits (100m/100Mi) for backend,
cache, and web. Switch rolling update strategy to maxSurge=1,
maxUnavailable=0 so new pods start before old ones terminate.
Add readiness probe to cache deployment.
- Public search dedup: CTE with ROW_NUMBER picks best edition per series
(nearest future, fallback most recent past), COUNT(*) OVER for edition_count
- Add EditionCount to Market model and MarketSummary DTO
- GetBySlug returns nearest-future edition by default, supports ?year= override
- GetBySlug returns sibling edition briefs for edition switcher UI
- New GET /admin/markets/grouped endpoint returns AdminSeriesGroup[] with
series-level pagination and all editions per group
- Add AdminSearchGrouped to repository, service, handler, and routes
Admin endpoint POST /admin/series/:id/editions creates a new yearly
edition for an existing series, inheriting series-level defaults.
Public market submissions now auto-match against existing series using
trigram similarity (>0.6) on name + exact city match. Matched
submissions link to the existing series instead of creating duplicates.
Fix Gin route tree conflict by nesting markets and series groups under
a shared /admin parent group.
Split markets into market_series (persistent identity) + market_editions
(yearly instances). Migration preserves edition UUIDs from old market IDs
for URL backward compatibility. Edition statuses: rumored, confirmed,
active, completed, cancelled, archived.
AI research now uses two-pass pipeline: Pass 1 via pre-created Mistral
agent with web search for structured extraction, Pass 2 via
mistral-large-latest for description writing and field retry. Confidence
scoring is source-derived (source count + extraction type) instead of
LLM self-reported.
- AI research via Mistral API for admin market editing
- Auto-geocoding via Nominatim OSM with rate limiting
- Public geocode endpoint (POST /api/v1/geocode)
- Duplicate detection using pg_trgm trigram similarity
- Extended SubmitMarketRequest with street, opening_hours, admission_info
- pg_trgm migration for fuzzy name matching
- Add PUT /auth/password for setting/changing passwords (handles both
first-time set for magic link/OAuth users and change for password users)
- Generate random medieval display names (e.g. Gaukler1025) for new
magic link and OAuth users instead of leaving display_name empty
- Add has_password field to ProfileData response
maxSurge=1 requires a second pod during rollout, but the tenant
ResourceQuota (1 CPU limit) is already at 900m — the extra 250m
exceeds the cap and the pod can't schedule, causing a 5min timeout.
Switch to maxSurge=0/maxUnavailable=1 (kill-then-start) to stay
within quota. Matches the web deployment strategy.
The public Search and admin List handlers share structure but use
different types (SearchParams/AdminSearchParams, MarketSummary/
AdminMarketSummary). This is intentional, not a refactoring target.
- Add SMTP_PORT, SMTP_FROM, ADMIN_EMAIL, FRONTEND_URL to ConfigMap
- Add Helm-managed SMTP secret for credentials (host, user, password)
- Wire Woodpecker secrets into deploy step via --set flags
- SMTP secret conditionally created only when values are provided
Add styled HTML email templates for market submission notifications
and magic link authentication, matching the frontend's forest green
and gold design language.
- Template engine using go:embed with clone-per-content pattern
- Multipart/alternative MIME (text/plain + text/html) in SMTPSender
- Base layout: dark green header, gold accent, parchment tones
- Market submission template with details table and admin CTA
- Magic link template with CTA button and fallback URL
- Wire email sending into MagicLinkHandler (replaces TODO)
- Add FRONTEND_URL config for template links
- Admin CRUD endpoints for markets with role-based middleware
- Anonymous market submission with Cloudflare Turnstile verification
- SMTP email notifications on new submissions (LogSender fallback)
- Market status workflow (pending/approved/rejected) with admin notes
- Nullable location column for submissions without coordinates
- CLI tool for promoting users to admin role
- Slug generation package extracted from seed
- Rate limiting on submission endpoint (3/hour per IP)
- Mailpit added to docker-compose for local email testing
- Reduced from 312 to 272 markets (removed 39 unverified/unconfirmed entries)
- All 272 markets now have real descriptions sourced from official websites
- Added admission prices (adult/child cents + notes) where available
- Added street addresses and corrected venue names
- Added opening hours for markets where published
- Updated websites to full https:// URLs
- Updated seedMarket struct with new fields: Street, Description,
AdmissionAdultCents, AdmissionChildCents, AdmissionNotes
- Seed INSERT now uses description/admission from JSON instead of
generating template descriptions; falls back to generated desc if empty
- Added jsonString() helper for SQL-safe JSON encoding
Single-replica deployment with tight CPU quota (1 core) cannot run two
pods simultaneously during a rolling update. Recreate kills the old pod
before starting the new one.
Scrape marktkalendarium.de for 2026 market data and replace the
placeholder seed with real events across DE/AT/CH.
- Embed 311 markets as JSON with name, dates, city, zip, venue,
organizer and website
- Geocode coordinates via Nominatim with caching and rate limiting
- Auto-derive Bundesland from postal code prefix
- Generate descriptions based on event type keywords
- Support DATABASE_URL env var for direct production seeding
Tenant SA lacks dragonflydb.io CRD permissions. Use a standard
Valkey Deployment+Service instead. Also re-enable CNPG (created
via kubectl), migrate job, and add seccompProfile to migrate pod.
- Disable revive exported/package-comments rules (style, not correctness)
- Use errors.Is instead of == for pgx.ErrNoRows comparisons
- Use errors.As instead of type assertion on validator errors
- Use http.NewRequestWithContext instead of client.Get (noctx)
- Check resp.Body.Close error return (errcheck)
- Run gofmt on files with formatting drift