UNPKG

@opengis/fastify-table

Version:

core-plugins

247 lines (198 loc) 9.14 kB
import path from 'node:path'; import { existsSync, readFileSync } from 'node:fs'; import { config, getPG, getTemplate, getSelectMeta, getMeta, applyHook, getSelectVal, logger, getSelect, } from '../../../../utils.js'; const limit = 50; const headers = { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET', 'Cache-Control': 'no-cache', }; async function getTableColumnMeta(tableName, column, filtered, pg) { if (!tableName || !column) { return null; } const loadTable = await getTemplate('table', tableName); const { data: clsName } = loadTable?.columns?.find?.(el => el.name === column) || {}; const { arr } = await getSelect(clsName, pg) || {}; const original = { true: `with c(id,text) as (select ${column} as id, ${column} as text from ${loadTable?.table || tableName} group by ${column}) select id, text from c`, false: `with c(id,text) as (select ${column} as id, ${column} as text, count(*) from ${loadTable?.table || tableName} group by ${column} limit ${limit}) select * from c`, }[!!filtered]; return { arr, original, searchQuery: '(lower("text") ~ $1 )', pk: 'id', }; } export default async function suggest(req) { const { params, query, pg: pg1, user, } = req; const lang = query.lang || 'ua'; const time = Date.now(); const parent = query.parent || ''; if (params?.data && params.data?.startsWith?.('hash-')) { const filepath = path.join(process.cwd(), `/log/suggest/${params.data.replace('hash-', '')}.json`); if (existsSync(filepath)) { const { table, column } = JSON.parse(readFileSync(filepath, { encoding: 'utf8' }) || '{}'); params.data = `${table}:${column}`; } } const [table, column] = params.data?.includes(':') ? params.data.split(':') : [query.token, query.column]; const selectName = query.sel || query.name || params.data; if (!selectName) return { status: 400, message: 'name is required' }; const { body: hookBody } = table || query.token ? await applyHook('preSuggest', { pg: pg1, table: table || query.token }) || {} : {}; const body = await getTemplate('table', table || query.token); const tableName = hookBody?.table || body?.table || table || query.token; if (table && !pg1.pk?.[tableName]) { return { status: 400, message: 'param name is invalid: 1' }; } const tableMeta = await getMeta({ pg: pg1, table: tableName }); const columnExists = (hookBody?.columns || body?.columns || tableMeta?.columns)?.find(col => col?.name === (column || query.column)); if (table && (!column || !columnExists)) { return { status: 400, message: 'param name is invalid: 2' }; } if (query.limit && query.limit < 0) { return { status: 400, message: 'param limit is invalid' }; } const meta = tableName && column ? await getTableColumnMeta(table, column, query?.key || query?.val, pg1) : await getSelectMeta({ pg: pg1, name: selectName, nocache: query?.nocache, parent, }); if (meta?.minLength && query.key && query.key.length < meta?.minLength) { return { message: `min length: ${meta.minLength}` }; } const pg = meta?.db ? getPG(meta.db) : pg1; if (!meta) { return { headers, status: 404, message: 'Not found query select ' }; } if (query.meta) { return meta; } const { arr, searchQuery } = meta; if (arr && query.token && query.column) { const loadTable = await getTemplate('table', query.token); const { columns = [] } = await getMeta({ pg, table: loadTable?.table || tableName }); const column1 = columns.find(el => el.name === query.column); const args = { table: tableName }; if (!column1) return []; const sqlCls = query.count ? `select value, count(*) from (select ${pg.pgType?.[column1.dataTypeID]?.includes('[]') ? `unnest(${column1.name})` : `${column1.name}`} as value from ${(loadTable?.table || tableName).replace(/'/g, "''")} where ${hookBody?.query || loadTable?.query || '1=1'})q group by value` : `select array_agg(distinct value)::text[] from (select ${pg.pgType?.[column1.dataTypeID]?.includes('[]') ? `unnest(${column1.name})` : `${column1.name}`} as value from ${(loadTable?.table || tableName).replace(/'/g, "''")} where ${hookBody?.query || loadTable?.query || '1=1'})q`; if (query.sql && (config.local || user?.user_type?.includes?.('admin'))) { return sqlCls; } if (pg.pk?.[loadTable?.table || tableName] && column1?.name) { const qRes = await pg.queryCache(sqlCls, args); const vals = (query.count ? qRes.rows?.map?.(el => el.value?.toString?.()) : qRes.rows?.[0]?.array_agg) || []; const lower = query.key?.toLowerCase?.(); const data1 = query.key || query.val ? arr?.filter((el) => !lower || (el[lang] || el.text)?.toLowerCase()?.indexOf(lower) !== -1)?.filter((el) => !query.val || el.id === query.val) : arr; const data2 = data1.filter((el) => el.id && vals.includes(el.id.toString())); const data = data2.slice(0, Math.min(query.limit || limit, limit)); if (data.length && query.count) { const counts = qRes.rows?.reduce?.((acc, curr) => ({ ...acc, [curr.value]: curr.count }), {}); data.forEach(el => Object.assign(el, { count: counts?.[el.id] || 0 })); } if (config.debug) { logger.file('suggest/debug', { type: 1, loadTable: loadTable?.table, tableName, column: column?.name, data, data1, data2, query, }); } return { time: Date.now() - time, limit: Math.min(query.limit || limit, limit), count: data.length, total: arr.length, mode: 'array', sql: (config.local || user?.user_type?.includes?.('admin')) ? sqlCls : undefined, data, }; } } if (arr) { const lower = query.key?.toLowerCase(); const data1 = query.key || query.val ? arr?.filter((el) => !lower || (el[lang] || el.text)?.toLowerCase()?.indexOf(lower) !== -1)?.filter((el) => !query.val || el.id === query.val) : arr; const data = data1.slice(0, Math.min(query.limit || limit, limit)); if (config.debug) { logger.file('suggest/debug', { type: 2, key: query.key, data, data1, }); } return { time: Date.now() - time, limit: Math.min(query.limit || limit, limit), count: data.length, total: arr.length, mode: 'array', data, }; } // search const search = query.key ? searchQuery : null; // val // const pk = meta.originalCols.split(',')[0]; // return meta; const val = query.val ? ` ${meta.pk}=any('{${query.val.replace(/'/g, "''")}}')` : ''; const where = [search, val, meta.pk ? `${meta.pk} is not null` : null].filter((el) => el).join(' and ') || 'true'; const loadTable = await getTemplate('table', query.token); const tableName1 = hookBody?.table || loadTable?.table || query.token; const { columns = [] } = await getMeta({ pg: pg1, table: tableName1 }); const { name: filterColumn, dataTypeID } = query.column ? (columns.find(el => el.name === query.column) || {}) : {}; const filter = query.token && pg.pk?.[loadTable?.table || query.token] && filterColumn && dataTypeID ? `id in (select ${pg.pgType[dataTypeID]?.includes('[]') ? `unnest(${filterColumn})` : filterColumn} from ${(loadTable?.table || query.token).replace(/'/g, "''")})` : 'true'; const sqlSuggest = `with c(id,text) as ( ${meta.original.replace(/{{parent}}/gi, parent)} where ${where} order by 2) select * from c where ${filter} limit ${Math.min(query.limit || meta.limit || limit, limit)}`.replace(/{{uid}}/g, user?.uid || '0'); if (query.sql && (config.local || user?.user_type?.includes?.('admin'))) { return sqlSuggest; } // query const { rows: dataNew } = await pg.query(sqlSuggest, query.key ? [`${query.key.toLowerCase()}`] : []); // const { rows: dataNew1 } = dataNew.length < limit ? await pg.query(sqlSuggest, query.key ? [`${query.key}`] : []) : {}; // const ids = dataNew.map((el) => el.id); const data = dataNew.filter(el => el.id && el.text); if (query.sel) { const clsData = await getSelectVal({ pg, name: query.sel, values: data.map((el) => el.id), }); data.forEach(el => Object.assign(el, { text: clsData?.[el.id || ''] || el.id })); } if (config.debug) { logger.file('suggest/debug', { type: 3, sel: query.sel, data, dataNew, sqlSuggest, // ids, }); } const message = { time: Date.now() - time, limit: Math.min(query.limit || meta.limit || limit, limit), count: data.length, total: meta.count - 0, mode: 'sql', db: meta.db, sql: (config.local || user?.user_type?.includes?.('admin')) ? sqlSuggest : undefined, data, }; return message; }