@opengis/fastify-table
Version:
core-plugins
247 lines (198 loc) • 9.14 kB
JavaScript
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;
}