// @ts-nocheck import type { FastifyInstance } from 'fastify'; import { channelRepository } from '@core/storage/repositories/channels.js'; import { validateInput, searchSchema } from '@core/security/validation.js'; import { formatNumber, formatRelativeTime, getSortField } from '@core/utils/helpers.js'; export async function searchRoutes(app: FastifyInstance) { // Home page - redirects to channels list app.get('/', async (request, reply) => { return reply.redirect('/channels'); }); // Search API endpoint (for HTMX live search) app.get('/api/search', async (request, reply) => { const validation = validateInput(searchSchema, request.query); const params = validation.success ? validation.data : { q: '', sort: 'newest', page: 1, limit: 20 }; const tag = request.query.tag as string | undefined; const search = params.q; const sort = params.sort; const page = params.page; const limit = Math.min(params.limit, 50); const offset = (page - 1) * limit; const { field, order } = getSortField(sort); const [channels, total] = await Promise.all([ channelRepository.findPublished({ tag, search, sortBy: field, sortOrder: order, limit, offset, }), channelRepository.countPublished({ ...(tag && { tags: tag }) }), ]); return reply.send({ channels: channels.map(c => ({ id: c.id, name: c.name, description: c.description, iconUrl: c.iconUrl, followerCount: c.followerCount, formattedFollowers: formatNumber(c.followerCount), voteCount: c.voteCount, formattedVotes: formatNumber(c.voteCount), tags: c.tags, relativeTime: formatRelativeTime(c.createdAt), ownerUsername: c.ownerUsername, })), pagination: { page, totalPages: Math.ceil(total / limit), total, hasNext: page * limit < total, hasPrev: page > 1, }, }); }); // Tag autocomplete app.get('/api/tags', async (request, reply) => { const query = (request.query.q as string || '').toLowerCase(); const channels = await channelRepository.findPublished({ limit: 1000 }); const tags = new Set(); channels.forEach(c => { c.tags.forEach(t => { if (!query || t.includes(query)) { tags.add(t); } }); }); return reply.send(Array.from(tags).sort().slice(0, 20)); }); // Sitemap app.get('/sitemap.xml', async (request, reply) => { const channels = await channelRepository.findPublished({ limit: 50000 }); const baseUrl = 'https://your-domain.com'; // TODO: from config const urls = [ { url: baseUrl, changefreq: 'daily', priority: '1.0' }, { url: `${baseUrl}/channels`, changefreq: 'hourly', priority: '0.9' }, ]; channels.forEach(c => { urls.push({ url: `${baseUrl}/channels/${c.id}`, lastmod: c.lastScrapedAt || c.updatedAt, changefreq: 'daily', priority: '0.7', }); }); const xml = ` ${urls.map(u => ` ${u.url} ${u.lastmod ? new Date(u.lastmod).toISOString().split('T')[0] : new Date().toISOString().split('T')[0]} ${u.changefreq} ${u.priority} `).join('\n')} `; reply.header('Content-Type', 'application/xml'); return reply.send(xml); }); // Robots.txt app.get('/robots.txt', async (request, reply) => { const baseUrl = 'https://your-domain.com'; // TODO: from config const content = `User-agent: * Allow: / Sitemap: ${baseUrl}/sitemap.xml `; reply.header('Content-Type', 'text/plain'); return reply.send(content); }); }