seepuls/CLAUDE.md
Till JS 32f0295a8b seepuls α-0: Repo-Skelett + Aggregator-Schema
Erste Vereins-App, die fremde Drittseiten crawlt und aggregiert
(Event-Übersicht Konstanz/Kreuzlingen, DE+CH zusammen). Phase-0-
Build dependency-arm (manaspur-Pattern): keine @mana/* via
Verdaccio, Föderations-Boilerplate inline.

- Hono+Bun-API mit health/manifest/search/takedown/dsgvo
- Drizzle-Schema (8 Tabellen) gemäß AGGREGATOR_POLICY §6:
  countries, regions, venues, event_sources, events,
  crawl_jobs (mit robots_check_passed-Compliance-Beweis),
  takedown_requests, blocked_domains
- Crawl-Policy-Skelett (assertCrawlAllowed) mit Block-List-Check
  + Pro-Host-Rate-Limit; robots.txt-Lookup pending α-1
- mana-research- und mana-geocoding-Client-Stubs
- Search-Endpoint public (kein Auth — Browse-only MVP)
- Postgres lokal :5441, db:push grün, /healthz + /readyz +
  /.well-known/mana-app.json smoke-getestet

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 14:14:15 +02:00

176 lines
7.7 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# CLAUDE.md — Seepuls
Guidance für Claude Code in diesem Repo.
> **Wenn du gerade neu bist:** lies zuerst [`STATUS.md`](STATUS.md) — dort
> steht der aktuelle Phasen-Stand. Dieses CLAUDE.md ist nur die
> Konventions- und Architektur-Referenz.
## Was dieses Repo ist
**Seepuls** — Event-Aggregator für die Grenzregion Konstanz/Kreuzlingen
(Bodensee, DE+CH zusammen). Föderierte App des **mana e.V.**-Ökosystems.
Erste Vereins-App, die **fremde Drittseiten crawlt, aggregiert und
re-publiziert** — gilt die [`AGGREGATOR_POLICY.md`](../mana/docs/AGGREGATOR_POLICY.md)
in `mana/docs/`.
```
HTTPS ┌──────────────────┐
mana e.V. ◄─────────────── │ seepuls/ │ Postgres `seepuls`
Plattform │ (this repo) │ seepuls.mana.how
• mana-research (crawl) │ Astro-Web + │
• mana-geocoding (DE/CH-Geo) │ Hono-API │
• mana-media (Bilder) └──────────────────┘
• mana-auth (Phase β) ▲
│ HTTPS
┌─────────┴────────┐
│ Drittseiten │
│ (Kulturhaus etc.)│
│ via Firecrawl │
└──────────────────┘
```
## Status
**Phase α-0 — Repo-Skelett (2026-05-15).** Manifest + API-Boilerplate
stehen, DB-Schema und Crawler-Pipeline noch nicht implementiert.
Roadmap siehe [`STATUS.md`](STATUS.md).
## Architektonische Invarianten
Beschlossen. Nicht ohne explizite Diskussion antasten.
1. **Aggregator-Policy ist nicht-verhandelbar.** robots.txt, ≥1s
Crawl-Delay, User-Agent mit Kontakt-Mail, sichtbare Attribution,
Take-Down-Endpoint binnen 72h vorläufige Deaktivierung. Siehe
[`AGGREGATOR_POLICY.md`](../mana/docs/AGGREGATOR_POLICY.md).
2. **Cross-Border by design.** Default-Ansicht zeigt DE+CH gemeinsam.
`countries`-Tabelle mit ISO-Codes (DE/CH), `regions`-Tabelle als
curated Klammer (`bodensee`, `hegau`, `thurgau-west`).
3. **mana-research als Crawl-Provider.** Eigene Crawler vermeiden;
Firecrawl/Jina respektieren robots.txt by default. Cost-Tracking
geht über mana-credits.
4. **Hot-Link für Bilder (Default).** Bilder von Drittseiten werden
nicht lokal gehostet — `<img src="originalquelle">`. Lokales
Caching nur als begründete Ausnahme.
5. **MVP ohne User-Auth.** Browse + Take-Down sind public; Claim-Flow
und Lieblings-Venue-Abo kommen mit mana-auth ab Phase β-3.
6. **Web ist Astro mit Hono-API**, nicht SvelteKit. Begründung in
Plan: Discovery-/SEO-Last, hohe statische Seitenzahl, Mobile-First.
7. **Eigene Postgres-DB `seepuls`** im geteilten Mana-Cluster
(lokal :5441), Schema-Isolation via `pgSchema('seepuls')`.
## Stack
| Layer | Wahl |
|---|---|
| Web | Astro (Node-Adapter, SSR + Islands für Filter+Karte) |
| API | Hono + Bun |
| Datenbank | PostgreSQL + Drizzle ORM |
| Auth | `@mana/shared-hono/auth` (JWKS aus mana-auth, ab β-3) |
| Föderation | `@mana/shared-app-tpl` für Manifest/Share/Tools/DSGVO |
| Crawl | `mana-research` (Firecrawl/Jina/Readability) |
| Geo | `mana-geocoding` (Photon Europe, DE+CH) |
| Storage | `mana-media` (nur App-eigene Bilder, nicht gecrawlte) |
| Job-Scheduling | `node-cron` in-process (kein Plattform-Scheduler) |
Ports: API `3095`, Web `3096`, Postgres `5441` (siehe
[`../mana/docs/PORTS.md`](../mana/docs/PORTS.md)).
## Repo-Struktur
```
seepuls/
├── apps/
│ ├── api/ Hono+Bun-Backend (Phase α-0+)
│ │ └── src/
│ │ ├── index.ts App-Boot + shared-app-tpl-Mounts
│ │ ├── handlers.ts Share-/Tool-Handler (leer im MVP)
│ │ ├── store.ts Datenzugriff (Drizzle, später)
│ │ └── db/schema/ Drizzle-Schema (folgt α-0)
│ └── web/ Astro-Frontend (Phase α-4, später)
├── packages/ leer für jetzt
├── docs/ repo-eigene Doks
├── app-manifest.json Föderations-Manifest (validiert beim Boot)
├── docker-compose.yml lokales Postgres :5441
├── STATUS.md aktueller Phasen-Stand
└── .github/workflows/ ci.yml
```
## Datenmodell (Plan, gemäß AGGREGATOR_POLICY §6)
```
countries DE, CH (hartcodiert)
regions bodensee, hegau, thurgau-west, … (curated)
venues Org/Lokal-Eintrag, gecrawlt oder geclaimt
event_sources N pro venue; Listing-URL + Crawl-Intervall
events Einzel-Event, deduplikiert via external_id_hash
crawl_jobs Audit-Trail: was wurde wann gecrawlt, mit
robots_check_passed-Flag (Compliance-relevant)
takedown_requests DSGVO-/Urheber-Anfragen mit Frist-Tracking
blocked_domains Source-Block-Liste (Eskalations-Stufe 2)
```
## Föderations-Vertrag
Pflicht-Endpoints (alle aus `@mana/shared-app-tpl`):
| Method | Path | Quelle |
|---|---|---|
| `GET` | `/healthz`, `/readyz`, `/healthz/details` | `healthRoutes()` |
| `GET` | `/.well-known/mana-app.json` | `wellKnownManifestHandler()` |
| `POST` | `/api/v1/share/receive` | `shareReceiveRouter()` (Handler leer im MVP) |
| `POST` | `/api/v1/tools/:name` | `toolsRouter()` (Handler leer im MVP) |
| `GET` | `/api/v1/search?q=` | app-eigen, public (keine Auth) |
| `GET` | `/api/v1/dsgvo/export` | `dsgvoExportHandler()` (takedown-Mails) |
Manifest-Drift-Check beim Boot — Service startet nicht, wenn das
Manifest nicht zum aktuellen Schema-Stand passt.
## Konventionen
- **pnpm 9.15.x**, Node 20+, Turborepo 2.x, Bun für API-Runtime
- **Tabs** für Indent, single quotes, 100-col Prettier
- **Drizzle 0.38 / drizzle-kit 0.30** (Plattform-Einklang)
- **JWT-Validation lokal** über JWKS-Cache (5 min), kein Live-Call
- **Service-to-Service** via `X-Service-Key` (`MANA_SERVICE_KEY`)
- **Tests**: Vitest
## Crawler-Disziplin (Pflicht)
Jeder Crawl muss durch `src/crawl/policy.ts` (folgt α-1) gehen, die
folgendes prüft und protokolliert:
- `robots.txt`-Lookup pro Host (gecached 24h)
- User-Agent: `SEEPULS_CRAWL_USER_AGENT` aus env, mit `+https://seepuls.mana.how; kontakt@mana.how`
- ≥ 1.1s zwischen Requests pro Host (`SEEPULS_CRAWL_INTERVAL_MS`)
- ETag/If-Modified-Since vom letzten Crawl mitgeben
- Backoff bei 429/503 (exponential, min 6h)
- Audit-Log in `crawl_jobs` mit `robots_check_passed: bool` — Compliance-Beweis
Bei Verstößen: `mana-compliance`-Subagent kann ein Veto auf einen Live-
Crawl-Cut werfen. Pre-Live-Check ist Pflicht.
## Lokal entwickeln
```bash
pnpm install # @mana/* aus pkg.mana.how
pnpm docker:up || docker compose up -d # lokales Postgres :5441
pnpm --filter ./apps/api dev # API auf :3095
# (apps/web folgt in α-4)
```
Voraussetzungen:
- mana-Plattform-Stack lokal laufend (mindestens `mana-auth` :3001,
`mana-research` :3068, `mana-geocoding` :3075)
- Verdaccio-Token in `~/.npmrc`
(`npm login --registry=https://pkg.mana.how`)
## Wichtige Cross-Repo-Doks
- [`../mana/docs/AGGREGATOR_POLICY.md`](../mana/docs/AGGREGATOR_POLICY.md) — Crawl-Regeln, nicht-verhandelbar
- [`../mana/docs/FEDERATION.md`](../mana/docs/FEDERATION.md) — Architektur-Grundlage
- [`../mana/docs/PORTS.md`](../mana/docs/PORTS.md) — Port-Allokation
- [`../mana/services/mana-research/CLAUDE.md`](../mana/services/mana-research/CLAUDE.md) — Crawl-Provider
- [`../mana/services/mana-geocoding/CLAUDE.md`](../mana/services/mana-geocoding/CLAUDE.md) — DE+CH-Geo