managarten/services/mana-analytics/src/index.ts
Till JS dbe24acfc4 feat(feedback,credits): community-credit grants — +5 submit / +500 ship / +25 reaction-match
Phase 3.A des feedback-rewards-and-identity-Plans. Direkter Reziprozitäts-
Loop: User kriegt sofort etwas zurück fürs Mitwirken, Originalwunsch-
Eulen werden beim Ship belohnt, Reagierer kriegen einen Anteil.

mana-credits:
- Neuer Endpoint POST /api/v1/internal/credits/grant + grantCredits()
  Service-Methode mit Idempotency via metadata.referenceId.
- transaction_type-Enum erweitert um 'grant' (eigener Typ statt
  Mismatch mit 'refund').
- Migration 0001_grant_transaction_type.sql + partial-Index auf
  metadata->>'referenceId' für O(log n) Idempotency-Lookup.

mana-analytics:
- FeedbackService stempelt sofort +5 Credits beim createFeedback (top-
  level only, Replies bekommen nichts), wenn Mindest-20-Zeichen erfüllt
  und Rate-Limit (10/User/24h via feedback_grant_log) nicht überschritten.
- adminUpdate triggert beim FRISCHEN Übergang nach 'completed':
  +500 Credits an Original-Wisher + +25 an alle, die mit 👍 oder 🚀
  reagiert haben. Doppel-Pay strukturell unmöglich via referenceId
  (`<id>_shipped`, `<id>_reaction_<userId>`).
- Founder-Whitelist via FEEDBACK_FOUNDER_USER_IDS env (verhindert
  Self-Reward).
- Drop voteCount-Spalte (durch reactions/score seit 0002 ersetzt).
- Migration 0003_grant_log_drop_vote_count.sql idempotent, lokal +
  prod eingespielt.

Plan: docs/plans/feedback-rewards-and-identity.md (Phase 3.A-3.F).

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

51 lines
1.5 KiB
TypeScript

/**
* mana-analytics — Public-Community Feedback Hub
*
* Hono + Bun runtime. Routes:
* /api/v1/feedback/* — auth-required (jwtAuth via JWKS)
* /api/v1/public/feedback/* — read-only, no auth, redacted output
*/
import { Hono } from 'hono';
import { cors } from 'hono/cors';
import { loadConfig } from './config';
import { getDb } from './db/connection';
import { serviceErrorHandler as errorHandler } from '@mana/shared-hono';
import { jwtAuth } from './middleware/jwt-auth';
import { FeedbackService } from './services/feedback';
import { healthRoutes } from './routes/health';
import { createFeedbackRoutes } from './routes/feedback';
import { createPublicFeedbackRoutes } from './routes/public';
const config = loadConfig();
const db = getDb(config.databaseUrl);
const feedbackService = new FeedbackService(
db,
config.manaLlmUrl,
config.pseudonymSecret,
config.manaCreditsUrl,
config.serviceKey,
config.founderUserIds
);
const app = new Hono();
app.onError(errorHandler);
app.use('*', cors({ origin: config.cors.origins, credentials: true }));
app.route('/health', healthRoutes);
// Public surface: anonymous reads, no JWT required.
app.route('/api/v1/public/feedback', createPublicFeedbackRoutes(feedbackService));
// Authenticated surface: submit, react, manage own items, admin.
app.use('/api/v1/feedback/*', jwtAuth(config.manaAuthUrl));
app.route('/api/v1/feedback', createFeedbackRoutes(feedbackService));
console.log(`mana-analytics starting on port ${config.port}...`);
export default {
port: config.port,
fetch: app.fetch,
};