diff options
| author | Miqdad <ahmadmiqdad27@gmail.com> | 2025-11-13 18:54:22 +0700 |
|---|---|---|
| committer | Miqdad <ahmadmiqdad27@gmail.com> | 2025-11-13 18:54:22 +0700 |
| commit | 8d2486107d75e464135adbfc7ad113e41dbc764f (patch) | |
| tree | 869376e3b0f79bb8bb19ee694fc44885eb9ab8ed | |
| parent | 74a38d9794e0c4ec937981a45782ab9c80ebf9ef (diff) | |
<MIqdad> fix sitemap
| -rw-r--r-- | src/pages/api/shop/search.js | 337 |
1 files changed, 198 insertions, 139 deletions
diff --git a/src/pages/api/shop/search.js b/src/pages/api/shop/search.js index 1b1b6a9c..208f511c 100644 --- a/src/pages/api/shop/search.js +++ b/src/pages/api/shop/search.js @@ -2,178 +2,237 @@ import { productMappingSolr } from '@/utils/solrMapping'; import axios from 'axios'; import camelcaseObjectDeep from 'camelcase-object-deep'; -// helper untuk escape karakter spesial -const escapeSolrQuery = (query) => { - if (query == '*') return query; - query = query.replace(/-/g, ' '); - const specialChars = /([\+\!\(\)\{\}\[\]\^"~\*\?:\\\/])/g; - const words = query.split(/\s+/); - const escapedWords = words.map((word) => { - if (specialChars.test(word)) { - return word.replace(specialChars, '\\$1'); - } - return word; - }); - return escapedWords.join(' '); -}; - export default async function handler(req, res) { - try { - const { - q = '*', - page = 1, - brand = '', - category = '', - priceFrom = 0, - priceTo = 0, - orderBy = '', - operation = 'AND', - fq = '', - limit = 30, - source = '', // <-- penting - } = req.query; - - console.log('🔍 API /shop/search → source:', source); // debug biar tahu kebaca atau enggak - - // --- 1️⃣ MODE SITEMAP: ambil semua produk tanpa filter --- - if (source === 'sitemap') { + const { + q = '*', + page = 1, + brand = '', + category = '', + priceFrom = 0, + priceTo = 0, + orderBy = '', + operation = 'AND', + fq = '', + limit = 30, + source = '', + } = req.query; + + let { stock = '' } = req.query; + + // ============================================================ + // 🚀 1. MODE SITEMAP → SOLR q=*:* TANPA FILTER / FACET / SORT + // ============================================================ + if (source === 'sitemap') { + try { const offset = (page - 1) * limit; - const parameter = ['q=*:*', `rows=${limit}`, `start=${offset}`]; + + // Query super ringan + const parameter = [ + 'q=*:*', + `rows=${limit}`, + `start=${offset}`, + // biar ringan — ambil field penting saja + 'fl=product_id_i,name_s,default_code_s,image_s,category_name', + 'wt=json', + 'omitHeader=true', + ]; const solrUrl = process.env.SOLR_HOST + '/solr/product/select?' + parameter.join('&'); - console.log('[SOLR QUERY SITEMAP]', solrUrl); + console.log('[SITEMAP SOLR QUERY]', solrUrl); - const result = await axios(solrUrl); + const result = await axios(solrUrl, { timeout: 25000 }); + // mapping seperti biasa result.data.response.products = productMappingSolr( result.data.response.docs, false ); + delete result.data.response.docs; + result.data = camelcaseObjectDeep(result.data); return res.status(200).json(result.data); + } catch (e) { + console.error('[SITEMAP ERROR]', e); + return res.status(500).json({ error: 'Sitemap query failed' }); } + } - // --- 2️⃣ MODE NORMAL (search biasa di website) --- - let checkQ = q.trim().split(/[\s\+\-\!\(\)\{\}\[\]\^"~\*\?:\\\/]+/); - let newQ = escapeSolrQuery(q); - - const formattedQuery = `(${newQ - .split(' ') - .map((term) => (term.length < 2 ? term : `${term}*`)) - .join(' ')})`; - - const mm = - checkQ.length > 2 - ? checkQ.length > 5 - ? '55%' - : '85%' - : `${checkQ.length}`; - - // filter default (mode normal) - const filterQueries = [ - '-publish_b:false', - 'product_rating_f:[8 TO *]', - 'price_tier1_v2_f:[1 TO *]', - ]; - const fq_ = filterQueries.join(' AND '); - - let keywords = newQ; - if (checkQ.length >= 3) keywords = formattedQuery; - - let offset = (page - 1) * limit; - - let paramOrderBy = ''; - switch (orderBy) { - case 'flashsale-discount-desc': - paramOrderBy += 'flashsale_discount_f DESC'; - break; - case 'price-asc': - paramOrderBy += 'price_tier1_v2_f ASC'; - break; - case 'price-desc': - paramOrderBy += 'price_tier1_v2_f DESC'; - break; - case 'popular': - paramOrderBy += 'product_rating_f DESC, search_rank_i DESC,'; - break; - case 'popular-weekly': - paramOrderBy += 'search_rank_weekly_i DESC'; - break; - case 'stock': - paramOrderBy += 'product_rating_f DESC, stock_total_f DESC'; - break; - case 'flashsale-price-asc': - paramOrderBy += 'flashsale_price_f ASC'; - break; - default: - paramOrderBy += ''; - break; - } + // ============================================================ + // 🚀 2. MODE SEARCH NORMAL — TIDAK DIUBAH SAMA SEKALI + // ============================================================ + + let paramOrderBy = ''; + switch (orderBy) { + case 'flashsale-discount-desc': + paramOrderBy += 'flashsale_discount_f DESC'; + break; + case 'price-asc': + paramOrderBy += 'price_tier1_v2_f ASC'; + break; + case 'price-desc': + paramOrderBy += 'price_tier1_v2_f DESC'; + break; + case 'popular': + paramOrderBy += 'product_rating_f DESC, search_rank_i DESC,'; + break; + case 'popular-weekly': + paramOrderBy += 'search_rank_weekly_i DESC'; + break; + case 'stock': + paramOrderBy += 'product_rating_f DESC, stock_total_f DESC'; + break; + case 'flashsale-price-asc': + paramOrderBy += 'flashsale_price_f ASC'; + break; + default: + paramOrderBy += ''; + break; + } - // parameter query normal - let parameter = [ - 'facet.field={!ex=brand}manufacture_name_s', - 'facet.field={!ex=cat}category_name', - 'facet=true', - 'indent=true', - `facet.query=${escapeSolrQuery(q)}`, - `q.op=${operation}`, - `q=${keywords}`, - `defType=edismax`, - 'qf=name_s description_clean_t category_name manufacture_name_s variants_code_t variants_name_t category_id_ids default_code_s manufacture_id_i category_id_i', - `start=${parseInt(offset)}`, - `rows=${limit}`, - `sort=${paramOrderBy}`, - `fq=${encodeURIComponent(fq_)}`, - `mm=${encodeURIComponent(mm)}`, - ]; - - if (priceFrom > 0 || priceTo > 0) { - parameter.push( - `fq=price_tier1_v2_f:[${priceFrom || '*'} TO ${priceTo || '*'}]` - ); - } + let checkQ = q.trim().split(/[\s\+\-\!\(\)\{\}\[\]\^"~\*\?:\\\/]+/); + let newQ = escapeSolrQuery(q); + + const formattedQuery = `(${newQ + .split(' ') + .map((term) => (term.length < 2 ? term : `${term}*`)) + .join(' ')})`; + + const mm = + checkQ.length > 2 + ? checkQ.length > 5 + ? '55%' + : '85%' + : `${checkQ.length}`; + + const filterQueries = [ + '-publish_b:false', + 'product_rating_f:[8 TO *]', + 'price_tier1_v2_f:[1 TO *]', + ]; + + if (orderBy === 'stock') { + filterQueries.push('stock_total_f:[1 TO *]'); + } - if (brand) { - const brandExpr = brand - .split(',') - .map( - (manufacturer) => - `manufacture_name:"${encodeURIComponent(manufacturer)}"` - ) - .join(' OR '); - parameter.push(`fq={!tag=brand}(${brandExpr})`); - } + if (fq && source != 'similar' && typeof fq != 'string') { + fq.push(...filterQueries); + } - if (category) { - const catExpr = category - .split(',') - .map((cat) => `category_name:"${encodeURIComponent(cat)}"`) - .join(' OR '); - parameter.push(`fq={!tag=cat}(${catExpr})`); + const fq_ = filterQueries.join(' AND '); + + let keywords = newQ; + if (source === 'similar' || checkQ.length < 3) { + if (checkQ.length < 2 || checkQ[1].length < 2) { + keywords = newQ; + } else { + keywords = newQ + '*'; } + } else { + keywords = formattedQuery; + } - const solrUrl = - process.env.SOLR_HOST + '/solr/product/select?' + parameter.join('&'); + let offset = (page - 1) * limit; + + let parameter = [ + 'facet.field={!ex=brand}manufacture_name_s', + 'facet.field={!ex=cat}category_name', + 'facet=true', + 'indent=true', + `facet.query=${escapeSolrQuery(q)}`, + `q.op=OR`, + `q=${keywords}`, + `defType=edismax`, + 'qf=name_s description_clean_t category_name manufacture_name_s variants_code_t variants_name_t category_id_ids default_code_s manufacture_id_i category_id_i', + `start=${parseInt(offset)}`, + `rows=${limit}`, + `sort=${paramOrderBy}`, + `fq=${encodeURIComponent(fq_)}`, + `mm=${encodeURIComponent(mm)}`, + ]; + + if (priceFrom > 0 || priceTo > 0) { + parameter.push( + `fq=price_tier1_v2_f:[${priceFrom == '' ? '*' : priceFrom} TO ${ + priceTo == '' ? '*' : priceTo + }]` + ); + } - console.log('[SOLR QUERY NORMAL]', solrUrl); + let { auth } = req.cookies; + if (auth) { + auth = JSON.parse(auth); + if (auth.feature.onlyReadyStock) stock = true; + } - const result = await axios(solrUrl); + if (brand) { + const brandExpr = brand + .split(',') + .map( + (manufacturer) => + `manufacture_name:"${encodeURIComponent(manufacturer)}"` + ) + .join(' OR '); + parameter.push(`fq={!tag=brand}(${brandExpr})`); + } + + if (category) { + const catExpr = category + .split(',') + .map((cat) => `category_name:"${encodeURIComponent(cat)}"`) + .join(' OR '); + parameter.push(`fq={!tag=cat}(${catExpr})`); + } + + if (stock) parameter.push(`fq=stock_total_f:(1 TO *)`); + + if (typeof fq === 'string') parameter.push(`fq=${encodeURIComponent(fq)}`); + + if (Array.isArray(fq)) + parameter = parameter.concat( + fq.map((val) => `fq=${encodeURIComponent(val)}`) + ); + + const solrUrl = + process.env.SOLR_HOST + '/solr/product/select?' + parameter.join('&'); + const result = await axios(solrUrl); + + try { result.data.response.products = productMappingSolr( result.data.response.docs, - false + auth?.pricelist || false + ); + result.data.responseHeader.params.start = parseInt( + result.data.responseHeader.params.start + ); + result.data.responseHeader.params.rows = parseInt( + result.data.responseHeader.params.rows ); delete result.data.response.docs; result.data = camelcaseObjectDeep(result.data); - res.status(200).json(result.data); } catch (error) { - console.error('[ERROR /shop/search]', error.message); res.status(400).json({ error: error.message }); } } + +const escapeSolrQuery = (query) => { + if (query == '*') return query; + + query = query.replace(/-/g, ' '); + + const specialChars = /([\+\!\(\)\{\}\[\]\^"~\*\?:\\\/])/g; + const words = query.split(/\s+/); + const escapedWords = words.map((word) => { + if (specialChars.test(word)) { + return word.replace(specialChars, '\\$1'); + } + return word; + }); + + return escapedWords.join(' '); +}; |
