Commit Graph

58 Commits

Author SHA1 Message Date
74ee825039 fix(helm): switch migrate init container from busybox to alpine
busybox:1.37 nc -z is broken (outputs "punt!" and hangs).
Alpine 3.21 ships a working nc -z implementation.
2026-03-31 23:38:50 +02:00
08d83bc57e fix(helm): replace broken nc -z in migrate job init container
BusyBox 1.37 nc -z outputs "punt!" and hangs. Use nc -w 2 with
stdin redirect instead, which correctly tests TCP connectivity.
2026-03-31 23:06:51 +02:00
ab2484474e fix(helm): remove busybox init container blocking backend startup
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.
2026-03-31 23:02:26 +02:00
1b329b8222 fix(ci): update deploy namespace to tenant-2 2026-03-09 16:19:27 +01:00
9c051df350 feat(helm): add wait-for-cache init container to backend deployment
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.
2026-03-08 20:00:52 +01:00
3d17e25764 fix(helm): bump dragonfly memory limit to 512Mi
DragonflyDB requires 256MiB minimum per thread. With container
overhead the 256Mi limit is insufficient, causing immediate exit.
2026-03-08 19:43:05 +01:00
b00e8df6db fix(helm): lower resource limits to fit within tenant-quota (1 CPU)
Set backend and cache limits to 200m/256Mi to stay within the
tenant-1 ResourceQuota of 1 CPU total.
2026-03-08 19:29:50 +01:00
2e1eed543d feat(helm): use DragonflyDB operator CRD, add HTTPRoute sectionName and HTTP→HTTPS redirects
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.
2026-03-08 19:01:34 +01:00
41876cd698 fix: remove completion_args from agent conversation request
Mistral rejects completion_args when agent_id is set. The agent's
own configuration (set in Mistral UI) controls response format.
2026-03-05 21:37:37 +01:00
209efe5309 fix: override response format to text for agent conversations
Mistral rejects json response format when tools (web search) are
active. Override to text since the prompt already requests JSON
output in the instructions.
2026-03-05 21:32:03 +01:00
c7085e5337 chore: bump Go to 1.26 in CI and Dockerfile
Required by mistral-go-sdk which targets go 1.26.
2026-03-05 21:25:58 +01:00
340132626c refactor: replace hand-rolled Mistral HTTP client with mistral-go-sdk
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.
2026-03-05 21:21:41 +01:00
02a03c3d41 feat: pass AI and Turnstile secrets via Helm deploy pipeline
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.
2026-03-05 18:41:42 +01:00
7937f43a06 fix: resolve golangci-lint dupl and goconst violations
Add nolint:dupl to Update handler (structurally similar to CreateEdition
by design). Extract "NULL" string literal to sqlNull constant.
2026-03-05 18:31:53 +01:00
bec253506e chore: normalize resources to 100m/100Mi and enable zero-downtime deploys
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.
2026-03-05 18:24:58 +01:00
d6e6d35ae5 feat: series-grouped market display with edition switching
- 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
2026-03-05 18:11:27 +01:00
6f743210f5 feat: add edition creation for existing series, auto-match on submit
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.
2026-03-05 17:08:13 +01:00
b5ac2620bf feat: market series/editions model, two-pass AI research, source-derived confidence
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.
2026-03-05 16:38:43 +01:00
10da87735f feat: add sort and order params to admin market list
Supports sorting by name, city, date, created, status with asc/desc order.
2026-03-05 15:22:01 +01:00
aa7a982caf feat: add AI research, geocoding, duplicate detection, enriched market submit
- 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
2026-03-05 15:18:44 +01:00
f839f32ddc chore: disable OAuth routes until provider apps are configured 2026-02-27 14:54:22 +01:00
841a69c59a feat: add password change endpoint, random medieval display names
- 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
2026-02-27 14:38:02 +01:00
fd879ba026 fix(deploy): use maxSurge=0 for rolling update to fit resource quota
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.
2026-02-27 14:17:02 +01:00
2def99d163 fix: switch backend deployment to RollingUpdate for zero downtime
maxUnavailable=0 ensures old pod stays up until new pod passes
readiness probes. maxSurge=1 allows one extra pod during rollout.
2026-02-27 13:43:04 +01:00
ac892720df fix: suppress dupl lint for Search handler
The public Search and admin List handlers share structure but use
different types (SearchParams/AdminSearchParams, MarketSummary/
AdminMarketSummary). This is intentional, not a refactoring target.
2026-02-27 13:34:37 +01:00
2e5d7b726b feat: add SMTP config to Helm chart and Woodpecker pipeline
- 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
2026-02-27 13:31:37 +01:00
e07f4c4c64 feat: add HTML email templates with medieval aesthetic
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
2026-02-27 11:36:59 +01:00
af7703b644 fix: replace ASCII-encoded umlauts with proper German characters in user-facing strings
Notification email and submission response used ae/oe instead of ä/ö.
2026-02-27 11:12:39 +01:00
580b9d5e3c feat: add admin panel, market submissions, and email notifications
- 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
2026-02-27 11:03:44 +01:00
cd92e84696 chore: add pre-commit hook configuration
Local hooks for hygiene (whitespace, YAML, merge conflicts, large files),
Go toolchain (fmt, vet, mod tidy, build), and golangci-lint matching CI.
2026-02-23 05:22:30 +01:00
d23ddba1ca fix: skip logging for health and readiness probe endpoints
Suppress log output for /healthz and /readyz to reduce noise from
Kubernetes liveness/readiness probes hitting every 10 seconds.
2026-02-22 20:26:58 +01:00
cb2e8c4cde fix(seed): gofmt struct field alignment 2026-02-22 19:13:53 +01:00
78046848f2 feat(seed): enrich markets data with real web-researched info
- 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
2026-02-22 19:09:21 +01:00
8b478a11b8 fix(deploy): use Recreate strategy to fit tenant CPU quota
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.
2026-02-22 12:01:50 +01:00
3236318e72 fix(deploy): add resource limits to migrate job to fit tenant quota 2026-02-22 11:55:42 +01:00
9e6608384c fix(deploy): increase migrate job deadline to 300s 2026-02-22 11:47:06 +01:00
549df60f09 fix(seed): resolve lint issues (noctx, goconst) 2026-02-22 11:41:33 +01:00
993bab1218 fix(seed): skip seeding if database already contains markets 2026-02-22 11:39:26 +01:00
ffaee89243 feat(seed): add 311 real medieval markets for 2026
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
2026-02-22 11:38:14 +01:00
e092a8d054 fix(deploy): replace Dragonfly CRD with plain Valkey deployment
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.
2026-02-22 10:53:33 +01:00
f48a29c433 fix(deploy): disable CNPG and migrate job (tenant SA lacks CRD permissions)
Postgres, Dragonfly, and NetworkPolicy must be provisioned by the
platform admin or via itsh.dev dashboard, not by the tenant SA.
2026-02-22 10:32:10 +01:00
7c2e2cebff fix(deploy): disable Dragonfly CRD (tenant SA lacks dragonflydb.io permission) 2026-02-22 10:25:50 +01:00
e99ab896d3 fix(deploy): disable NetworkPolicy (tenant SA lacks networkpolicies permission) 2026-02-22 10:22:18 +01:00
ae54910f51 fix(docker): use existing nobody user instead of creating UID 65534 2026-02-22 10:19:19 +01:00
f07eac1811 fix(ci): add gcc/musl-dev for race detector in test step 2026-02-22 10:15:17 +01:00
a796443b1c fix(lint): remove revive linter (only producing doc-comment noise) 2026-02-22 10:13:42 +01:00
3e42f59f96 fix(lint): exclude revive exported/package-comments via issues filter 2026-02-22 10:11:42 +01:00
3145dba255 fix(lint): resolve all golangci-lint v2 issues
- 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
2026-02-22 10:09:46 +01:00
dd5366f931 fix(lint): remove gosimple (merged into staticcheck in golangci-lint v2) 2026-02-22 10:05:37 +01:00
4b871c67cc fix(lint): remove goimports from linters (formatter in golangci-lint v2) 2026-02-22 10:04:09 +01:00