Neuer Endpoint POST /api/v1/decks/from-image akzeptiert bis zu 5 Bilder (PNG/JPG/WebP, max 10 MiB je) oder PDFs (max 30 MiB je) als multipart/form-data. Alle Dateien werden in einem einzigen mana-llm Vision-Call verarbeitet (mana/vision → llava → Gemini 2.5-flash → GPT-4o Fallback-Chain). PDFs werden von Gemini nativ verstanden (Layout, Tabellen, Bilder im Dokument) ohne Zwischenschritt über Text-Extraktion oder Rendering. Der google.py-Provider reicht den MIME-Type aus dem data:-URI direkt an types.Part.from_bytes() weiter. - llm-client: chatVisionJson() mit images[]-Array (mehrere Bilder/Dokumente) - decks-generate: GeneratedDeckSchema + insertGeneratedDeck() exportiert - decks-from-image: neuer Route-Handler, MIME-Filter für image/* + application/pdf - index: neue Route gemountet - client.ts: apiForm() für multipart-Uploads ohne JSON.stringify - decks.ts: generateDeckFromImage(files, opts) - NewDeckCard + /decks/new: Dropzone mit Multi-File, Thumbnail-Strip, PDF-Icon Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
89 lines
3.7 KiB
TypeScript
89 lines
3.7 KiB
TypeScript
import { Hono } from 'hono';
|
|
import { cors } from 'hono/cors';
|
|
|
|
import { manifestRoute } from './routes/manifest.ts';
|
|
import { healthRoute } from './routes/health.ts';
|
|
import { decksRouter } from './routes/decks.ts';
|
|
import { cardsRouter } from './routes/cards.ts';
|
|
import { reviewsRouter } from './routes/reviews.ts';
|
|
import { shareRouter } from './routes/share.ts';
|
|
import { toolsRouter } from './routes/tools.ts';
|
|
import { searchRouter } from './routes/search.ts';
|
|
import { dsgvoRouter } from './routes/dsgvo.ts';
|
|
import { meRouter } from './routes/me.ts';
|
|
import { mediaRouter } from './routes/media.ts';
|
|
import { decksGenerateRouter } from './routes/decks-generate.ts';
|
|
import { decksFromImageRouter } from './routes/decks-from-image.ts';
|
|
import { authorsRouter as marketplaceAuthorsRouter } from './routes/marketplace/authors.ts';
|
|
import { marketplaceDecksRouter } from './routes/marketplace/decks.ts';
|
|
import { exploreRouter as marketplaceExploreRouter } from './routes/marketplace/explore.ts';
|
|
import { engagementRouter as marketplaceEngagementRouter } from './routes/marketplace/engagement.ts';
|
|
import { subscriptionsRouter as marketplaceSubscriptionsRouter } from './routes/marketplace/subscriptions.ts';
|
|
import { forkRouter as marketplaceForkRouter } from './routes/marketplace/fork.ts';
|
|
import { pullRequestsRouter as marketplacePullRequestsRouter } from './routes/marketplace/pull-requests.ts';
|
|
import { discussionsRouter as marketplaceDiscussionsRouter } from './routes/marketplace/discussions.ts';
|
|
|
|
const app = new Hono();
|
|
|
|
app.use(
|
|
'*',
|
|
cors({
|
|
origin: (origin) => {
|
|
if (!origin) return origin;
|
|
// Dev: localhost-Ports erlaubt. Prod: explizite Whitelist.
|
|
if (/^https?:\/\/localhost(:\d+)?$/.test(origin)) return origin;
|
|
if (/^https?:\/\/127\.0\.0\.1(:\d+)?$/.test(origin)) return origin;
|
|
if (origin === 'https://cardecky.mana.how') return origin;
|
|
return null;
|
|
},
|
|
allowHeaders: ['Content-Type', 'X-User-Id', 'Authorization', 'X-Service-Key'],
|
|
allowMethods: ['GET', 'POST', 'PATCH', 'DELETE', 'OPTIONS'],
|
|
credentials: true,
|
|
})
|
|
);
|
|
|
|
app.route('/', healthRoute);
|
|
app.route('/.well-known/mana-app.json', manifestRoute);
|
|
app.route('/api/v1/decks', decksRouter());
|
|
app.route('/api/v1/cards', cardsRouter());
|
|
app.route('/api/v1/reviews', reviewsRouter());
|
|
app.route('/api/v1/share', shareRouter());
|
|
app.route('/api/v1/tools', toolsRouter());
|
|
app.route('/api/v1/search', searchRouter());
|
|
app.route('/api/v1/dsgvo', dsgvoRouter());
|
|
app.route('/api/v1/me', meRouter());
|
|
app.route('/api/v1/media', mediaRouter());
|
|
app.route('/api/v1/decks/generate', decksGenerateRouter());
|
|
app.route('/api/v1/decks/from-image', decksFromImageRouter());
|
|
|
|
// Marketplace (Phase 12). Eigenes pgSchema, additive Routen unter /v1/marketplace/*.
|
|
// Plan: docs/playbooks/MARKETPLACE_RESTORE.md.
|
|
//
|
|
// Mount-Reihenfolge ist signifikant: spezifischere Routes vor /authors
|
|
// und /decks, damit z.B. /marketplace/me/subscriptions nicht zu authors
|
|
// oder decks geroutet wird.
|
|
app.route('/api/v1/marketplace', marketplaceExploreRouter());
|
|
app.route('/api/v1/marketplace', marketplaceEngagementRouter());
|
|
app.route('/api/v1/marketplace', marketplaceSubscriptionsRouter());
|
|
app.route('/api/v1/marketplace', marketplaceForkRouter());
|
|
app.route('/api/v1/marketplace', marketplacePullRequestsRouter());
|
|
app.route('/api/v1/marketplace', marketplaceDiscussionsRouter());
|
|
app.route('/api/v1/marketplace/authors', marketplaceAuthorsRouter());
|
|
app.route('/api/v1/marketplace/decks', marketplaceDecksRouter());
|
|
|
|
app.get('/', (c) =>
|
|
c.json({
|
|
app: 'cards',
|
|
version: process.env.CARDS_API_VERSION ?? '0.0.0',
|
|
see: '/.well-known/mana-app.json',
|
|
})
|
|
);
|
|
|
|
const port = Number(process.env.CARDS_API_PORT ?? 3081);
|
|
|
|
console.log(`[cards-api] listening on http://localhost:${port}`);
|
|
|
|
export default {
|
|
port,
|
|
fetch: app.fetch,
|
|
};
|