{"id":51240895,"url":"https://github.com/petronijus/kulturniprehled","last_synced_at":"2026-06-29T00:02:48.231Z","repository":{"id":365390304,"uuid":"1271628277","full_name":"petronijus/kulturniprehled","owner":"petronijus","description":"Self-hosted cultural-event tracker (concerts, theatre, cinema) shared between two users — Flutter mobile apps + FastAPI backend, with a Claude Code skill for ticket ingestion.","archived":false,"fork":false,"pushed_at":"2026-06-17T05:18:13.000Z","size":1993,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-17T07:14:23.813Z","etag":null,"topics":["claude-code","cultural-events","dart","fastapi","flutter","python","self-hosted"],"latest_commit_sha":null,"homepage":null,"language":"Dart","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"agpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/petronijus.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-16T21:23:34.000Z","updated_at":"2026-06-17T05:18:17.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/petronijus/kulturniprehled","commit_stats":null,"previous_names":["petronijus/kulturniprehled"],"tags_count":17,"template":false,"template_full_name":null,"purl":"pkg:github/petronijus/kulturniprehled","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/petronijus%2Fkulturniprehled","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/petronijus%2Fkulturniprehled/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/petronijus%2Fkulturniprehled/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/petronijus%2Fkulturniprehled/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/petronijus","download_url":"https://codeload.github.com/petronijus/kulturniprehled/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/petronijus%2Fkulturniprehled/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34907985,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-28T02:00:05.809Z","response_time":54,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["claude-code","cultural-events","dart","fastapi","flutter","python","self-hosted"],"created_at":"2026-06-29T00:02:47.555Z","updated_at":"2026-06-29T00:02:48.224Z","avatar_url":"https://github.com/petronijus.png","language":"Dart","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Kulturní Přehled\n\nSelf-hosted cultural-event tracker for two users (Petr + Běla), with mobile\napps for Android and iOS and a Claude Code skill that ingests purchased\ntickets and creates events automatically.\n\n## Features\n\n- Shared agenda of concerts, theatre, cinema and other cultural events —\n  chronological list, monthly calendar, dedicated past-events view, and a\n  per-event detail screen with cover art, venue photos, ticket PDFs, costs\n  and notes.\n- Shared watchlist (todo-style) of films, theatre and concerts to catch\n  later, with 2-level nesting (e.g. \"Godard\" → individual films),\n  drag-to-reorder, and 10-second polling so both devices stay in sync.\n- Year-in-review statistics — total spend, visits by category, top venues,\n  monthly distribution.\n- LLM-powered ticket ingestion: drop a ticket file into Claude on the\n  desktop and the event, metadata, transport details and the ticket itself\n  appear on both phones — offline.\n- **Offline-first mobile**: after each sync the app proactively pre-caches\n  every upcoming event's cover image and ticket PDF onto the device, then\n  garbage-collects past events. The agenda, detail screen, ticket viewer\n  and calendar all read from disk first and fall back to the network only\n  on cache misses. Tickets stay readable at venues without signal.\n- **Background sync**: a 30-minute WorkManager (Android) / BGTaskScheduler\n  (iOS) task pulls server changes and re-primes the cache while the app is\n  closed, so the first launch on event day is already up-to-date. No FCM /\n  APNs dependency — simpler ops, no push tokens to manage.\n- One-way sync to a shared Google Calendar (idempotent via\n  `extendedProperties.kp_event_id`).\n- CZK cost tracking with year-in-review statistics.\n- Offline edit queue: writes go into an outbox table and reconcile with\n  the server when the network returns; version conflicts surface a\n  \"keep yours / use server\" dialog.\n- Local notifications for event start time, configurable per-event.\n- Optional self-hosted GlitchTip / Sentry error reporting.\n- Infrastructure prepared for a future proactive AI recommendations agent.\n\n## Stack\n\n- **Backend** — Python 3.12, FastAPI, SQLAlchemy 2.0 async, PostgreSQL 16,\n  Alembic, MinIO (S3-compatible).\n- **Mobile** — Flutter 3.44.0 (Android primary, iOS feature parity), drift\n  (SQLite), Riverpod, go_router, Material 3, pdfrx, workmanager\n  (federated), sensors_plus, flutter_local_notifications.\n- **LLM** — Anthropic Claude API behind a `LLMProvider` abstraction.\n- **Public access** — Cloudflare Tunnel, two subdomains\n  (`kulturniprehled.example.com` for the API,\n  `kulturniprehled-tickets.example.com` for MinIO signed URLs).\n- **Auth** — Google OAuth2 (PKCE on mobile) + JWT (15 min) + refresh-token\n  rotation with reuse detection. Personal access tokens for headless\n  clients (the Claude skill).\n- **Mobile release** — signed APK from GitHub Releases for Android (Pixel\n  + Běla's phone sideload), TestFlight Internal Testing (group `Družina`)\n  for iOS.\n\nSee [`CLAUDE.md`](./CLAUDE.md) for the development guide, including the\nlocal pre-merge checklist, release procedure and iOS dev-sideload flow.\n\n## Repository layout\n\n```\napps/api/            FastAPI service\napps/mobile/         Flutter app\npackages/            OpenAPI snapshot\nskills/ticket-parser Claude Code skill source\nassets-source/       master design assets — user-authored, repo of truth\n  brand/             logo + launcher + notification icon masters; the\n                     downstream PNG/AppIcon variants are regenerated from\n                     these only when the masters change\ninfra/\n  docker-compose.yml    dev stack (Postgres + MinIO + API)\n  compose.prod.yml      prod overlay (cloudflared, hardened ports)\n  compose.glitchtip.yml optional error-reporting overlay\n  cloudflared/          tunnel config example\n  deploy/               setup-vm.sh + upgrade.sh + README\n  backup/               pg_dump, mc_mirror, restore-test, cron.example\ndocs/                Architecture, API, sync, deployment, handover notes\n```\n\n## Quick start (development)\n\nPrerequisites:\n- Docker and Docker Compose v2\n- Python 3.12+ (only needed for running backend tests outside Docker)\n- Flutter 3.44.0 (only needed for the mobile app)\n\n```bash\ncp .env.example .env\n# Edit .env — at minimum:\n#   GOOGLE_OAUTH_CLIENT_ID + SECRET (mine: 1Password \"google petr-apps OAuth client\")\n#   ANTHROPIC_API_KEY              (mine: 1Password \"Claude API Token\")\n#   ALLOWED_EMAILS                 (Petr + Běla)\n\ndocker compose --env-file .env -f infra/docker-compose.yml up -d\ncurl http://localhost:18000/healthz   # → {\"status\":\"ok\",...}\n```\n\nThe dev stack publishes Postgres on `15432`, MinIO API on `19000`\n(console `19001`) and the API on `18000` so it can co-exist with a system\nPostgres/MinIO.\n\n## Skill setup (desktop ingestion)\n\n```bash\n./scripts/mint-pat.sh petr@example.com 'desktop-skill'\n# script pipes the JWT straight into 1Password — never to stdout\n```\n\nThe skill itself lives in `skills/ticket-parser/`; symlink it into Claude\nCode's skills directory once:\n\n```bash\nln -sfn $(pwd)/skills/ticket-parser ~/.claude/skills/kulturni-prehled-ingest\n```\n\n## Testing\n\n```bash\n# Backend (testcontainers spins up Postgres + MinIO)\ncd apps/api\nuv sync --dev\nuv run pytest\n\n# Mobile\ncd apps/mobile\nflutter pub get\nflutter test\n```\n\nSee [`CLAUDE.md`](./CLAUDE.md) for the full pre-merge checklist (ruff,\nblack, mypy, dart format, flutter analyze).\n\n## Production deployment\n\nSee [`infra/deploy/README.md`](./infra/deploy/README.md). TL;DR:\n\n```bash\nsh infra/deploy/setup-vm.sh        # one-shot bootstrap\n# fill /opt/kp/.env, drop Cloudflare Tunnel creds in /etc/cloudflared\ndocker compose --env-file /opt/kp/.env \\\n               -f /opt/kp/infra/docker-compose.yml \\\n               -f /opt/kp/infra/compose.prod.yml \\\n               up -d\n# Wire infra/backup/cron.example into /etc/cron.d/kp\n```\n\nFor routine releases: `ssh deploy@kp-vm /opt/kp/infra/deploy/upgrade.sh`.\n\n## Mobile release\n\n- **Android** — local signed APK build, published to GitHub Releases. Petr\n  + Běla install via browser from\n  `https://github.com/petronijus/kulturniprehled/releases`. Procedure in\n  [`CLAUDE.md`](./CLAUDE.md) under *Release procedure*.\n- **iOS** — TestFlight Internal Testing (group `Družina`,\n  auto-distribute on). Two release paths:\n  - Local Mac with Xcode + app-specific password — original recipe in\n    [`CLAUDE.md`](./CLAUDE.md) under *iOS release procedure (TestFlight)*.\n  - Headless from any SSH-capable workstation via the Proxmox MacOS VM\n    + App Store Connect API key. End-to-end recipe lives in\n    [`docs/handover.md`](./docs/handover.md) under the 2026-05-22 session.\n\n## Milestones\n\n- **M0** Repo bootstrap.\n- **M1** Backend foundation — Google OAuth + JWT refresh rotation + events CRUD.\n- **M2** Sync API + `change_log` + outbox apply with idempotency.\n- **M3** Tickets + MinIO signed-URL flow.\n- **M4** Claude skill extension + Personal Access Tokens.\n- **M5** Flutter foundation — auth, agenda, offline-first sync.\n- **M6** Monthly calendar, ticket viewer, outbox + conflict UI.\n- **M7** Costs + year-in-review stats + PDF viewer.\n- **M8** Backups, deploy scripts, error reporting, polish, **v1.0.0 GA**.\n- **M9** Watchlist — shared todo list with 2-level nesting, drag reorder,\n  10-second sync polling.\n- **M10** UI polish round — Material 3 refresh of every screen per Figma\n  (agenda, detail, calendar, watchlist, stats), hero animations between\n  agenda and detail, parallax cover + device-tilt, bottom nav redesign.\n- **M11** Offline-first hardening + iOS TestFlight live —\n  proactive image + ticket cache after each sync, past-event GC,\n  WorkManager / BGTaskScheduler 30-min background refresh; **first iOS\n  TestFlight build delivered to Běla 2026-05-22**.\n- **M12** Proactive recommendations — Claude Code skill suite under\n  `skills/`: `kulturni-prehled` aggregator (weekly via `/schedule`,\n  the only skill that emails) + per-domain expert skills that produce\n  ranked candidate JSON. **Phase 1 (2026-05-23)** ships\n  `klasika-expert` (5 ensemble scrapers + festival WebFetch + Spotify\n  + Discogs) and `elektronika-expert` (Spotify + venue WebFetch).\n  Phase 2 will add `divadlo-expert` + `film-expert`.\n\n## License\n\n[AGPL-3.0](./LICENSE) — network copyleft: if you run a modified version as a\nnetwork service, you must make the source available to its users.\n\n## Secrets\n\n`.env` is gitignored and kept SOPS-encrypted in the private overlay\n(`private/config/env.sops`) so it syncs across machines without plaintext in\ngit. With the overlay cloned into `./private` and the 1Password CLI signed in,\nrun `./scripts/secrets-decrypt.sh` to materialize `.env` (the age private key is\nfetched from 1Password), or `./scripts/secrets-edit.sh` to change it.\nPrereqs: `brew install sops age`.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpetronijus%2Fkulturniprehled","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpetronijus%2Fkulturniprehled","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpetronijus%2Fkulturniprehled/lists"}