{"version":3,"file":"index.mjs","names":[],"sources":["../../../../../src/astro/routes/api/search/index.ts"],"sourcesContent":["/**\n * Search endpoint - Full-text search across collections\n *\n * GET /_emdash/api/search?q=query&collections=posts,pages&limit=20\n */\n\nimport { hasPermission } from \"@premium-cms/auth\";\nimport type { APIRoute } from \"astro\";\n\nimport { apiError, apiSuccess, handleError } from \"#api/error.js\";\nimport { isParseError, parseQuery } from \"#api/parse.js\";\nimport { searchQuery } from \"#api/schemas.js\";\nimport { searchWithDb } from \"#search/index.js\";\n\nexport const prerender = false;\n\n/**\n * Search content\n *\n * Query parameters:\n * - q: Search query (required)\n * - collections: Comma-separated list of collection slugs (optional, defaults to all)\n * - status: Filter by status (optional, defaults to 'published')\n * - limit: Maximum results (optional, defaults to 20)\n */\nexport const GET: APIRoute = async ({ url, locals }) => {\n\tconst { emdash, user } = locals;\n\n\tif (!emdash?.db) {\n\t\treturn apiError(\"NOT_CONFIGURED\", \"EmDash not configured\", 500);\n\t}\n\n\tconst query = parseQuery(url, searchQuery);\n\tif (isParseError(query)) return query;\n\n\tconst collections = query.collections\n\t\t? query.collections.split(\",\").map((c: string) => c.trim())\n\t\t: undefined;\n\n\t// Only users with content:read_drafts may search non-published statuses.\n\t// Anonymous and subscriber requests are forced to \"published\".\n\tconst status =\n\t\tquery.status && query.status !== \"published\" && hasPermission(user, \"content:read_drafts\")\n\t\t\t? query.status\n\t\t\t: \"published\";\n\n\ttry {\n\t\t// Verify FTS indexes are healthy on first use. At most once per worker\n\t\t// lifetime; no-op after that. Moved off the cold-start hot path to\n\t\t// keep anonymous public reads fast.\n\t\tawait emdash.ensureSearchHealthy?.();\n\n\t\tconst result = await searchWithDb(emdash.db, query.q, {\n\t\t\tcollections,\n\t\t\tstatus,\n\t\t\tlocale: query.locale,\n\t\t\tlimit: query.limit,\n\t\t\tcursor: query.cursor,\n\t\t});\n\n\t\treturn apiSuccess(result);\n\t} catch (error) {\n\t\t// handleError maps a malformed pagination cursor to a 400 INVALID_CURSOR.\n\t\treturn handleError(error, \"Search failed\", \"SEARCH_ERROR\");\n\t}\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAcA,MAAa,YAAY;;;;;;;;;;AAWzB,MAAa,MAAgB,OAAO,EAAE,KAAK,aAAa;CACvD,MAAM,EAAE,QAAQ,SAAS;AAEzB,KAAI,CAAC,QAAQ,GACZ,QAAO,SAAS,kBAAkB,yBAAyB,IAAI;CAGhE,MAAM,QAAQ,WAAW,KAAK,YAAY;AAC1C,KAAI,aAAa,MAAM,CAAE,QAAO;CAEhC,MAAM,cAAc,MAAM,cACvB,MAAM,YAAY,MAAM,IAAI,CAAC,KAAK,MAAc,EAAE,MAAM,CAAC,GACzD;CAIH,MAAM,SACL,MAAM,UAAU,MAAM,WAAW,eAAe,cAAc,MAAM,sBAAsB,GACvF,MAAM,SACN;AAEJ,KAAI;AAIH,QAAM,OAAO,uBAAuB;AAUpC,SAAO,WARQ,MAAM,aAAa,OAAO,IAAI,MAAM,GAAG;GACrD;GACA;GACA,QAAQ,MAAM;GACd,OAAO,MAAM;GACb,QAAQ,MAAM;GACd,CAAC,CAEuB;UACjB,OAAO;AAEf,SAAO,YAAY,OAAO,iBAAiB,eAAe"}