managarten/services/mana-events/src/app.ts
Till JS b5d55fdb21 feat(events): add Event Discovery — Phase 1 + 2
Phase 1: Manual iCal feeds + Discovery tab
- 5 new DB tables in event_discovery schema (regions, interests,
  sources, discovered_events, user_actions)
- iCal parser (node-ical) with deduplication (SHA-256 hash)
- Crawl scheduler (15-min interval, auto-deactivate after 5 errors)
- CRUD routes for regions, interests, sources + paginated feed endpoint
- Frontend: "Meine Events" / "Entdecken" tab navigation in ListView
- Discovery setup wizard (regions via mana-geocoding + interests)
- DiscoveredEventCard with save/dismiss, SourceManager for iCal feeds
- "Merken" creates a local socialEvent from discovered event

Phase 2: Auto source discovery + LLM extraction + relevance scoring
- Source discoverer: web search via mana-research to auto-find iCal
  feeds and venue websites for a region
- Website extractor: crawl via mana-research /extract, then LLM-based
  event extraction via mana-llm with structured JSON output
- Flexible date parsing (ISO, DD.MM.YYYY), markdown fence stripping
- Relevance scorer: category match, freetext match, haversine distance,
  time proximity, weekend bonus (0-100 clamped)
- Routes: POST regions/:id/discover-sources, PUT/DELETE sources/:id/activate|reject
- Frontend: "Automatisch finden" button, suggested vs active sources UI

107 tests (all passing), no regressions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-18 15:30:46 +02:00

53 lines
1.6 KiB
TypeScript

/**
* App factory — kept separate from index.ts so tests can import it
* without triggering the production bootstrap (sweeper, log, port bind).
*/
import { Hono, type MiddlewareHandler } from 'hono';
import { cors } from 'hono/cors';
import type { Config } from './config';
import type { Database } from './db/connection';
import { errorHandler } from './middleware/error-handler';
import { jwtAuth } from './middleware/jwt-auth';
import { healthRoutes } from './routes/health';
import { createEventsRoutes } from './routes/events';
import { createRsvpRoutes } from './routes/rsvp';
import { createDiscoveryRoutes } from './routes/discovery';
import { createDiscoveryFeedRoutes } from './routes/discovery-feed';
/**
* Build the Hono app. The auth middleware is injected so tests can swap
* the real JWKS-validating jwtAuth for a header-based mock without
* spinning up a real mana-auth instance.
*/
export function createApp(
db: Database,
config: Config,
authMiddleware: MiddlewareHandler = jwtAuth(config.manaAuthUrl)
): Hono {
const app = new Hono();
app.onError(errorHandler);
app.use(
'*',
cors({
origin: config.cors.origins,
credentials: true,
})
);
// Public — no auth
app.route('/health', healthRoutes);
app.route('/api/v1/rsvp', createRsvpRoutes(db, config));
// Authenticated host endpoints
app.use('/api/v1/events/*', authMiddleware);
app.route('/api/v1/events', createEventsRoutes(db));
// Discovery endpoints (all authenticated)
app.use('/api/v1/discovery/*', authMiddleware);
app.route('/api/v1/discovery', createDiscoveryRoutes(db, config));
app.route('/api/v1/discovery', createDiscoveryFeedRoutes(db));
return app;
}