import type { HttpContext } from '@adonisjs/core/http' import vine from '@vinejs/vine' import ScrapedPart from '#models/scraped_part' import ComponentType from '#models/component_type' import { ensureFreshCategory } from '#services/part_scraper_service' import { findCheapestOffers } from '#services/merchant_price_service' const searchValidator = vine.compile( vine.object({ q: vine.string().trim().minLength(1).optional(), typeId: vine.number().min(1).optional(), }) ) const compareValidator = vine.compile( vine.object({ id: vine.number().min(1), }) ) export default class ScrapedPartsController { /** * Search the new-parts cache (scraped from pc-builder.io) by name and/or * component type. Read-only, authenticated (the search UI lives inside * the authenticated "create component" flow). * * When a typeId is given, the corresponding category cache is refreshed * synchronously first if it's missing or older than 24h (see * part_scraper_service.CACHE_TTL_HOURS), so results stay reasonably * fresh without needing an external cron job. Free-text searches across * all types skip this eager refresh to avoid re-scraping every mapped * category on every keystroke; run `node ace scrape:parts` to refresh * everything at once. */ async index({ request, auth, response }: HttpContext) { await auth.authenticate() const { q, typeId } = await request.validateUsing(searchValidator) if (typeId) { const componentType = await ComponentType.find(typeId) if (componentType) { await ensureFreshCategory(componentType) } } const query = ScrapedPart.query().preload('type').orderBy('name') if (q) { query.whereRaw('LOWER(name) LIKE ?', [`%${q.toLowerCase()}%`]) } if (typeId) { query.where('typeId', typeId) } const parts = await query.limit(30) return response.json(parts.map((part) => part.serialize())) } /** * Compares live prices for a given scraped part across Amazon, LDLC, * Grosbill and TopAchat, and returns them sorted cheapest first. This is * triggered on demand (one part at a time) rather than for every search * result, since it makes several outbound HTTP requests and can take a * few seconds. */ async compare({ request, auth, response }: HttpContext) { await auth.authenticate() const { id } = await request.validateUsing(compareValidator) const part = await ScrapedPart.find(id) if (!part) { return response.notFound({ offers: [] }) } const amazonUrl = part.sourceUrl.includes('amazon.') ? part.sourceUrl : null const offers = await findCheapestOffers(part.name, amazonUrl) return response.json({ offers }) } }