From 9ca4e764383ffc3800fbe899dd7e07c297c51e75 Mon Sep 17 00:00:00 2001 From: Mqdd Date: Fri, 5 Dec 2025 10:50:31 +0700 Subject: fix query --- src/pages/api/shop/search.js | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) (limited to 'src/pages/api') diff --git a/src/pages/api/shop/search.js b/src/pages/api/shop/search.js index 42d16100..fec75fd8 100644 --- a/src/pages/api/shop/search.js +++ b/src/pages/api/shop/search.js @@ -210,6 +210,47 @@ export default async function handler(req, res) { fq.map((val) => `fq=${encodeURIComponent(val)}`) ); + // Searchkey + if (req.query.from === 'searchkey') { + const phrase = q.replace(/-/g, ' ').trim(); + + // encode q + const encodedQuery = encodeURIComponent(`variants_name_t:"${phrase}"`); + + const strictQuery = [ + `q=${encodedQuery}`, + `defType=edismax`, + `q.op=AND`, + `mm=${encodeURIComponent('100%')}`, + `qf=${encodeURIComponent('name_s description_clean_t')}`, + `rows=${limit}`, + `start=${(page - 1) * limit}`, + ]; + + if (fq) strictQuery.push(`fq=${encodeURIComponent(fq)}`); + + const solrUrl = + process.env.SOLR_HOST + '/solr/product/select?' + strictQuery.join('&'); + + console.log('[STRICT SEARCHKEY QUERY]', solrUrl); + + const result = await axios(solrUrl); + + try { + result.data.response.products = productMappingSolr( + result.data.response.docs, + auth?.pricelist || false + ); + + delete result.data.response.docs; + result.data = camelcaseObjectDeep(result.data); + + return res.status(200).json(result.data); + } catch (e) { + return res.status(400).json({ error: e.message }); + } + } + const solrUrl = process.env.SOLR_HOST + '/solr/product/select?' + parameter.join('&'); -- cgit v1.2.3 From 95b27ddb0604fbb4fae130f2d80e5ee2aec6d0fc Mon Sep 17 00:00:00 2001 From: Mqdd Date: Thu, 11 Dec 2025 09:00:03 +0700 Subject: fix --- src/pages/api/shop/search.js | 19 +++++++------------ src/pages/api/shop/searchkey.js | 31 +++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 12 deletions(-) create mode 100644 src/pages/api/shop/searchkey.js (limited to 'src/pages/api') diff --git a/src/pages/api/shop/search.js b/src/pages/api/shop/search.js index fec75fd8..d60b9a46 100644 --- a/src/pages/api/shop/search.js +++ b/src/pages/api/shop/search.js @@ -212,27 +212,22 @@ export default async function handler(req, res) { // Searchkey if (req.query.from === 'searchkey') { - const phrase = q.replace(/-/g, ' ').trim(); + const ids = req.query.ids ? req.query.ids.split(',').filter(Boolean) : []; - // encode q - const encodedQuery = encodeURIComponent(`variants_name_t:"${phrase}"`); + const q = ids.map((id) => `product_id_i:${id}`).join(' OR '); const strictQuery = [ - `q=${encodedQuery}`, - `defType=edismax`, - `q.op=AND`, - `mm=${encodeURIComponent('100%')}`, - `qf=${encodeURIComponent('name_s description_clean_t')}`, + `q=${encodeURIComponent(q)}`, + `fq=-publish_b:false AND price_tier1_v2_f:[1 TO *] AND product_rating_f:[8 TO *]`, + // `qf=variants_code_t variants_name_t`, `rows=${limit}`, - `start=${(page - 1) * limit}`, + `start=${offset}`, ]; - if (fq) strictQuery.push(`fq=${encodeURIComponent(fq)}`); - const solrUrl = process.env.SOLR_HOST + '/solr/product/select?' + strictQuery.join('&'); - console.log('[STRICT SEARCHKEY QUERY]', solrUrl); + console.log('[SEARCHKEY FINAL QUERY]', solrUrl); const result = await axios(solrUrl); diff --git a/src/pages/api/shop/searchkey.js b/src/pages/api/shop/searchkey.js new file mode 100644 index 00000000..e8b535e7 --- /dev/null +++ b/src/pages/api/shop/searchkey.js @@ -0,0 +1,31 @@ +import axios from 'axios'; + +export default async function handler(req, res) { + const { url = '', page = 1, limit = 30 } = req.query; + + let offset = (page - 1) * limit; + + const params = [ + `q.op=AND`, + `q=keywords_s:"${url}"`, + `indent=true`, + `rows=${limit}`, + `start=${offset}`, + ]; + + try { + // let result = await axios( + // process.env.SOLR_HOST + `/solr/searchkey/select?` + params.join('&') + // ); + let result = await axios.post( + process.env.SOLR_HOST + `/solr/searchkey/select`, + params.join('&'), + { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } } + ); + + console.log(result.data); + res.status(200).json(result.data); + } catch (error) { + res.status(500).json({ error: 'Internal Server Error' }); + } +} -- cgit v1.2.3 From 7cdb4cef31577818682b63ccbe01b53dd08a9207 Mon Sep 17 00:00:00 2001 From: Mqdd Date: Thu, 11 Dec 2025 09:31:51 +0700 Subject: sitemap done --- src/pages/api/shop/searchkey.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'src/pages/api') diff --git a/src/pages/api/shop/searchkey.js b/src/pages/api/shop/searchkey.js index e8b535e7..2735e72c 100644 --- a/src/pages/api/shop/searchkey.js +++ b/src/pages/api/shop/searchkey.js @@ -3,11 +3,18 @@ import axios from 'axios'; export default async function handler(req, res) { const { url = '', page = 1, limit = 30 } = req.query; + let q = '*:*'; + + if (!req.query.all) { + const url = (req.query.q || '').trim(); + q = `keywords_s:"${url}"`; + } let offset = (page - 1) * limit; const params = [ `q.op=AND`, - `q=keywords_s:"${url}"`, + // `q=keywords_s:"${url}"`, + `q=${q}`, `indent=true`, `rows=${limit}`, `start=${offset}`, -- cgit v1.2.3 From 5808e82529933aee7c63ede955f64028fd1f38ee Mon Sep 17 00:00:00 2001 From: Mqdd Date: Tue, 16 Dec 2025 14:34:47 +0700 Subject: fix bug & add category --- src/pages/api/shop/searchkey.js | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) (limited to 'src/pages/api') diff --git a/src/pages/api/shop/searchkey.js b/src/pages/api/shop/searchkey.js index 2735e72c..f5546a36 100644 --- a/src/pages/api/shop/searchkey.js +++ b/src/pages/api/shop/searchkey.js @@ -1,19 +1,23 @@ import axios from 'axios'; export default async function handler(req, res) { - const { url = '', page = 1, limit = 30 } = req.query; + const { url = '', page = 1, limit = 30, all } = req.query; let q = '*:*'; - if (!req.query.all) { - const url = (req.query.q || '').trim(); - q = `keywords_s:"${url}"`; + // ✅ kalau BUKAN sitemap + if (!all) { + const cleanUrl = url.trim(); + if (!cleanUrl) { + return res.status(400).json({ error: 'Missing url param' }); + } + q = `keywords_s:"${cleanUrl}"`; } - let offset = (page - 1) * limit; + + const offset = (page - 1) * limit; const params = [ `q.op=AND`, - // `q=keywords_s:"${url}"`, `q=${q}`, `indent=true`, `rows=${limit}`, @@ -21,18 +25,19 @@ export default async function handler(req, res) { ]; try { - // let result = await axios( - // process.env.SOLR_HOST + `/solr/searchkey/select?` + params.join('&') - // ); - let result = await axios.post( - process.env.SOLR_HOST + `/solr/searchkey/select`, + const result = await axios.post( + `${process.env.SOLR_HOST}/solr/searchkey/select`, params.join('&'), - { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } } + { + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + } ); - console.log(result.data); res.status(200).json(result.data); } catch (error) { + console.error(error?.response?.data || error); res.status(500).json({ error: 'Internal Server Error' }); } } -- cgit v1.2.3 From a03ddbe49706870862f692b6c425b4dce6175842 Mon Sep 17 00:00:00 2001 From: Mqdd Date: Fri, 19 Dec 2025 15:12:09 +0700 Subject: add param orderby seearchkey --- src/pages/api/shop/search.js | 1 + 1 file changed, 1 insertion(+) (limited to 'src/pages/api') diff --git a/src/pages/api/shop/search.js b/src/pages/api/shop/search.js index d60b9a46..5f77b5c6 100644 --- a/src/pages/api/shop/search.js +++ b/src/pages/api/shop/search.js @@ -222,6 +222,7 @@ export default async function handler(req, res) { // `qf=variants_code_t variants_name_t`, `rows=${limit}`, `start=${offset}`, + `sort=${paramOrderBy}`, ]; const solrUrl = -- cgit v1.2.3 From adb28e824ca2d05244ad939a273067e5b7e38f76 Mon Sep 17 00:00:00 2001 From: Mqdd Date: Thu, 19 Feb 2026 14:25:30 +0700 Subject: done all --- src/pages/api/shop/search.js | 188 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 159 insertions(+), 29 deletions(-) (limited to 'src/pages/api') diff --git a/src/pages/api/shop/search.js b/src/pages/api/shop/search.js index 1f636f28..8954446a 100644 --- a/src/pages/api/shop/search.js +++ b/src/pages/api/shop/search.js @@ -1,6 +1,11 @@ import { productMappingSolr } from '@/utils/solrMapping'; import axios from 'axios'; import camelcaseObjectDeep from 'camelcase-object-deep'; +import { + chunkArray, + buildProductIdOrClause, + mergeSolrResults, +} from '@/lib/utils/batchSolrQueries'; const escapeSolrQuery = (query) => { if (query == '*') return query; @@ -14,7 +19,26 @@ const escapeSolrQuery = (query) => { .join(' '); }; +/** + * Get parameter value from either GET (req.query) or POST (req.body) + * POST takes precedence if available + */ +const getParam = (req, key, defaultValue = undefined) => { + // POST takes precedence + if (req.body && req.body[key] !== undefined) { + return req.body[key]; + } + // Fallback to GET + if (req.query && req.query[key] !== undefined) { + return req.query[key]; + } + return defaultValue; +}; + export default async function handler(req, res) { + // Support both GET and POST requests + const params = req.method === 'POST' ? req.body : req.query; + const { q = '*', page = 1, @@ -28,9 +52,15 @@ export default async function handler(req, res) { limit = 30, source = '', group = 'true', - } = req.query; + } = params; - let { stock = '' } = req.query; + let { stock = '' } = params; + + // Convert string parameters to appropriate types (for POST requests) + const pageNum = parseInt(page, 10) || 1; + const limitNum = parseInt(limit, 10) || 30; + const priceFromNum = parseFloat(priceFrom) || 0; + const priceToNum = parseFloat(priceTo) || 0; // ============================================================ // [PERBAIKAN] 1. LOGIC KHUSUS COMPARE (PAKAI URLSearchParams) @@ -56,7 +86,7 @@ export default async function handler(req, res) { const params = new URLSearchParams(); params.append('q', qCompare); - params.append('rows', limit); + params.append('rows', limitNum); params.append('wt', 'json'); params.append('indent', 'true'); @@ -218,7 +248,7 @@ export default async function handler(req, res) { // ============================================================ if (source === 'sitemap') { try { - const offset = (page - 1) * limit; + const offset = (pageNum - 1) * limitNum; const parameter = [ 'q=*:*', `rows=${limit}`, @@ -311,7 +341,7 @@ export default async function handler(req, res) { keywords = formattedQuery; } - let offset = (page - 1) * limit; + let offset = (pageNum - 1) * limitNum; let parameter = [ 'facet.field={!ex=brand}manufacture_name_s', @@ -334,9 +364,9 @@ export default async function handler(req, res) { parameter.push(`fq=${encodeURIComponent(f)}`); }); - if (priceFrom > 0 || priceTo > 0) { + if (priceFromNum > 0 || priceToNum > 0) { parameter.push( - `fq=price_tier1_v2_f:[${priceFrom || '*'} TO ${priceTo || '*'}]`, + `fq=price_tier1_v2_f:[${priceFromNum || '*'} TO ${priceToNum || '*'}]`, ); } @@ -371,39 +401,139 @@ export default async function handler(req, res) { fq.map((val) => `fq=${encodeURIComponent(val)}`), ); - // Searchkey - if (req.query.from === 'searchkey') { - const ids = req.query.ids ? req.query.ids.split(',').filter(Boolean) : []; + // Searchkey with batching for large ID arrays + if (params.from === 'searchkey') { + try { + // Extract IDs from either query params (GET) or body (POST) + let ids = []; + + if (req.method === 'POST' && req.body && req.body.ids) { + // POST request: IDs in body + ids = Array.isArray(req.body.ids) + ? req.body.ids + : req.body.ids.split(',').filter(Boolean); + } else if (req.query.ids) { + // GET request: IDs in query params + ids = req.query.ids.split(',').filter(Boolean); + } - const q = ids.map((id) => `product_id_i:${id}`).join(' OR '); + if (ids.length === 0) { + return res.status(400).json({ error: 'No product IDs provided' }); + } - const strictQuery = [ - `q=${encodeURIComponent(q)}`, - `fq=-publish_b:false AND price_tier1_v2_f:[1 TO *] AND product_rating_f:[8 TO *]`, - // `qf=variants_code_t variants_name_t`, - `rows=${limit}`, - `start=${offset}`, - `sort=${paramOrderBy}`, - ]; + console.log(`[SEARCHKEY] Processing ${ids.length} product IDs`); - const solrUrl = - process.env.SOLR_HOST + '/solr/product/select?' + strictQuery.join('&'); + // If less than 100 IDs, use single query + if (ids.length <= 100) { + const q = buildProductIdOrClause(ids); - console.log('[SEARCHKEY FINAL QUERY]', solrUrl); + const strictQuery = [ + `q=${encodeURIComponent(q)}`, + `fq=-publish_b:false AND price_tier1_v2_f:[1 TO *] AND product_rating_f:[8 TO *]`, + `rows=${limitNum}`, + `start=${offset}`, + `sort=${paramOrderBy}`, + ]; - const result = await axios(solrUrl); + const solrUrl = + process.env.SOLR_HOST + + '/solr/product/select?' + + strictQuery.join('&'); - try { - result.data.response.products = productMappingSolr( - result.data.response.docs, + console.log('[SEARCHKEY SINGLE QUERY]', solrUrl); + + const result = await axios(solrUrl); + result.data.response.products = productMappingSolr( + result.data.response.docs, + auth?.pricelist || false, + ); + + delete result.data.response.docs; + result.data = camelcaseObjectDeep(result.data); + + return res.status(200).json(result.data); + } + + // Batch large ID arrays into chunks of 100 + const idChunks = chunkArray(ids, 100); + console.log( + `[SEARCHKEY BATCH] Splitting ${ids.length} IDs into ${idChunks.length} chunks`, + ); + + // Execute all chunk queries in parallel + const batchQueries = idChunks.map((chunk) => { + const q = buildProductIdOrClause(chunk); + const queryParams = [ + `q=${encodeURIComponent(q)}`, + `fq=-publish_b:false AND price_tier1_v2_f:[1 TO *] AND product_rating_f:[8 TO *]`, + `rows=100`, // Request maximum 100 rows per chunk (100 IDs per chunk) + `start=0`, + `sort=${paramOrderBy}`, + ]; + + const solrUrl = + process.env.SOLR_HOST + + '/solr/product/select?' + + queryParams.join('&'); + + console.log( + `[SEARCHKEY BATCH QUERY] Chunk: ${chunk.slice(0, 5).join(',')}...`, + ); + + return axios(solrUrl).catch((error) => { + console.error('[SEARCHKEY BATCH ERROR]', error.message); + throw error; + }); + }); + + // Wait for all queries to complete + const batchResults = await Promise.all(batchQueries); + + // Merge all documents from all chunks, removing duplicates + const allDocs = mergeSolrResults( + batchResults.map((r) => r.data.response.docs), + ); + + console.log( + `[SEARCHKEY MERGE] Merged ${allDocs.length} unique documents from ${batchResults.length} chunks`, + ); + + // Apply pagination on merged results + const paginatedDocs = allDocs.slice(offset, offset + limitNum); + + // Use first result's response structure as template + const templateResponse = batchResults[0].data; + + const mergedResponse = { + ...templateResponse, + response: { + ...templateResponse.response, + numFound: allDocs.length, + start: offset, + rows: limitNum, + docs: paginatedDocs, + }, + responseHeader: { + ...templateResponse.responseHeader, + params: { + ...templateResponse.responseHeader.params, + start: offset, + rows: limitNum, + }, + }, + }; + + mergedResponse.response.products = productMappingSolr( + paginatedDocs, auth?.pricelist || false, ); - delete result.data.response.docs; - result.data = camelcaseObjectDeep(result.data); + delete mergedResponse.response.docs; + mergedResponse.data = camelcaseObjectDeep(mergedResponse); - return res.status(200).json(result.data); + return res.status(200).json(mergedResponse.data); } catch (e) { + console.error('[SEARCHKEY ERROR]', e.message); return res.status(400).json({ error: e.message }); } } -- cgit v1.2.3 From 6aecb5c1a2ee384b8ea2847a543142bfaa9c48f2 Mon Sep 17 00:00:00 2001 From: Mqdd Date: Thu, 19 Feb 2026 16:51:50 +0700 Subject: remove console log --- src/pages/api/shop/search.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) (limited to 'src/pages/api') diff --git a/src/pages/api/shop/search.js b/src/pages/api/shop/search.js index 8954446a..f7220568 100644 --- a/src/pages/api/shop/search.js +++ b/src/pages/api/shop/search.js @@ -421,7 +421,7 @@ export default async function handler(req, res) { return res.status(400).json({ error: 'No product IDs provided' }); } - console.log(`[SEARCHKEY] Processing ${ids.length} product IDs`); + // console.log(`[SEARCHKEY] Processing ${ids.length} product IDs`); // If less than 100 IDs, use single query if (ids.length <= 100) { @@ -440,7 +440,7 @@ export default async function handler(req, res) { '/solr/product/select?' + strictQuery.join('&'); - console.log('[SEARCHKEY SINGLE QUERY]', solrUrl); + // console.log('[SEARCHKEY SINGLE QUERY]', solrUrl); const result = await axios(solrUrl); result.data.response.products = productMappingSolr( @@ -456,9 +456,9 @@ export default async function handler(req, res) { // Batch large ID arrays into chunks of 100 const idChunks = chunkArray(ids, 100); - console.log( - `[SEARCHKEY BATCH] Splitting ${ids.length} IDs into ${idChunks.length} chunks`, - ); + // console.log( + // `[SEARCHKEY BATCH] Splitting ${ids.length} IDs into ${idChunks.length} chunks`, + // ); // Execute all chunk queries in parallel const batchQueries = idChunks.map((chunk) => { @@ -494,9 +494,9 @@ export default async function handler(req, res) { batchResults.map((r) => r.data.response.docs), ); - console.log( - `[SEARCHKEY MERGE] Merged ${allDocs.length} unique documents from ${batchResults.length} chunks`, - ); + // console.log( + // `[SEARCHKEY MERGE] Merged ${allDocs.length} unique documents from ${batchResults.length} chunks`, + // ); // Apply pagination on merged results const paginatedDocs = allDocs.slice(offset, offset + limitNum); -- cgit v1.2.3