From dd3a702c377cabd7ab4ba514beca6d2d7ec85e8c Mon Sep 17 00:00:00 2001 From: arsam Date: Fri, 18 Sep 2026 23:20:36 +0330 Subject: [PATCH] Import source: app/backend/src/routes/rag-gateway Import the existing local project snapshot. This commit organizes existing files; it does not represent newly developed functionality. --- app/backend/src/routes/rag-gateway.ts | 51 +++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 app/backend/src/routes/rag-gateway.ts diff --git a/app/backend/src/routes/rag-gateway.ts b/app/backend/src/routes/rag-gateway.ts new file mode 100644 index 0000000..d940209 --- /dev/null +++ b/app/backend/src/routes/rag-gateway.ts @@ -0,0 +1,51 @@ +import type { FastifyPluginAsync, FastifyRequest } from 'fastify'; +import { z } from 'zod'; +import { prisma } from '../db.js'; +import { checkRateLimit, rateLimitResponse } from '../lib/rate-limit.js'; + +function stripHtmlToMarkdown(html: string): string { + if (!html) return ''; + return html + .replace(/]*>(.*?)<\/h[1-6]>/gi, '\n## $1\n') + .replace(/]*>(.*?)<\/p>/gi, '\n$1\n') + .replace(/]*href="([^"]+)"[^>]*>(.*?)<\/a>/gi, '[$2]($1)') + .replace(/]*>(.*?)<\/strong>/gi, '**$1**') + .replace(/]*>(.*?)<\/em>/gi, '*$1*') + .replace(/<[^>]+>/g, '') + .replace(/\n\s*\n/g, '\n\n') + .trim(); +} + +export const ragRoutes: FastifyPluginAsync = async (app) => { + app.get('/api/v1/rag/:slug', async (req, reply) => { + const { slug } = req.params as { slug: string }; + const article = await prisma.article.findUnique({ + where: { slug }, + select: { title: true, summary: true, content: true, dateModified: true } + }); + + if (!article) return reply.code(404).send('# Not Found'); + + reply.header('Content-Type', 'text/markdown; charset=utf-8'); + + let markdown = `# ${article.title}\n\n`; + if (article.summary) markdown += `> ${article.summary}\n\n`; + markdown += `Last Modified: ${article.dateModified?.toISOString()}\n\n`; + markdown += stripHtmlToMarkdown(article.content); + + return markdown; + }); + + app.post('/api/v1/search/semantic', async (req: FastifyRequest, reply) => { + const rl = await checkRateLimit(req.ip, 'semantic', 10, 60); + if (rl.limited) return rateLimitResponse(reply, rl.retryAfter); + + const schema = z.object({ query: z.string().min(1), limit: z.number().max(20).optional() }); + const parsed = schema.safeParse(req.body); + if (!parsed.success) return reply.code(400).send({ error: 'invalid_input' }); + + const { semanticSearch } = await import('../lib/embeddings.js'); + const results = await semanticSearch(parsed.data.query, parsed.data.limit || 5); + return { results }; + }); +};