import { Router } from 'express'; import { z } from 'zod'; import { UrlImportService } from '../services/UrlImportService.js'; import { SchemaOrgRecipeParserService } from '../services/SchemaOrgRecipeParserService.js'; import { HeuristicRecipeParserService } from '../services/HeuristicRecipeParserService.js'; const importUrlSchema = z.object({ url: z.string().url('A valid URL is required'), }); export function createImportRoutes(): Router { const router = Router(); const urlImportService = new UrlImportService(); const schemaOrgParser = new SchemaOrgRecipeParserService(); const heuristicParser = new HeuristicRecipeParserService(); /** * POST /api/import/url * Fetch an external recipe page and return imported, normalized Recipe (if found) */ router.post('/url', async (req, res) => { try { const { url } = importUrlSchema.parse(req.body); const result = await urlImportService.fetchFromUrl(url); // Try to parse and normalize Recipe from JSON-LD blocks let draft: any = null; for (const block of result.json_ld_blocks) { draft = schemaOrgParser.parseJsonLdBlock(block); if (draft) break; } // Fallback: heuristic HTML parser when Schema.org data is missing/invalid if (!draft) { draft = heuristicParser.parseHtml(result.html, result.source_url); } res.status(200).json({ success: true, data: { ...result, draft_recipe: draft }, error: null, }); } catch (error) { if (error instanceof z.ZodError) { res.status(400).json({ success: false, data: null, error: error.errors, }); return; } if (error instanceof Error) { const status = error.message.includes('timed out') ? 504 : 400; res.status(status).json({ success: false, data: null, error: error.message, }); return; } res.status(500).json({ success: false, data: null, error: 'Internal server error', }); } }); return router; }