stackpress
Version:
Incept is a content management framework.
152 lines (151 loc) • 7.03 kB
JavaScript
import Nest from '@stackpress/lib/Nest';
import { toSqlString, toSqlFloat, toSqlInteger, toSqlBoolean, toSqlDate, toResponse, toErrorResponse, getColumns, getColumnInfo, stringable, floatable, dateable, boolable, intable } from '../helpers.js';
export default async function search(model, engine, query = {}, seed) {
let { q, columns = ['*'], filter = {}, span = {}, sort = {}, skip = 0, take = 50, total: useTotal = true } = query;
columns = columns.map(column => getColumns(column, model)).flat();
const info = columns
.map(column => getColumnInfo(column, model))
.filter(column => column.path.length > 0);
const selectors = info.map(selector => {
const q = engine.dialect.q;
const column = selector.table.length > 0
? `${q}${selector.table}${q}.${q}${selector.column}${q}`
: `${q}${model.snake}${q}.${q}${selector.column}${q}`;
return `${column} AS ${q}${selector.alias}${q}`;
});
const select = engine.select(selectors).from(model.snake);
const count = engine
.select('COUNT(*) as total')
.from(model.snake);
const joins = {};
info.forEach(selector => Object.assign(joins, selector.joins));
Object.values(joins).forEach(({ table, from, to, alias }) => {
const q = engine.dialect.q;
select.join('inner', table, from.replaceAll('.', `${q}.${q}`), to.replaceAll('.', `${q}.${q}`), alias);
count.join('inner', table, from.replaceAll('.', `${q}.${q}`), to.replaceAll('.', `${q}.${q}`), alias);
});
if (skip) {
select.offset(skip);
}
if (take) {
select.limit(take);
}
if (q && model.searchables.length > 0) {
select.where(model.searchables.map(column => `${column.snake} ILIKE ?`).join(' OR '), model.searchables.map(_ => `%${q}%`));
count.where(model.searchables.map(column => `${column.snake} ILIKE ?`).join(' OR '), model.searchables.map(_ => `%${q}%`));
}
if (model.active) {
if (typeof filter[model.active.name] === 'undefined') {
filter[model.active.name] = true;
}
else if (filter[model.active.name] == -1) {
delete filter[model.active.name];
}
}
Object.entries(filter).forEach(([key, value]) => {
const info = getColumnInfo(key, model).last;
if (!info)
return;
const q = engine.dialect.q;
const selector = `${q}${info.model.snake}${q}.${q}${info.column.snake}${q}`;
value = info.column.serialize(value, undefined, seed);
const serialized = stringable.includes(info.column.type)
? toSqlString(value)
: floatable.includes(info.column.type)
? toSqlFloat(value)
: intable.includes(info.column.type)
? toSqlInteger(value)
: boolable.includes(info.column.type)
? toSqlBoolean(value)
: dateable.includes(info.column.type)
? toSqlDate(value)?.toISOString()
: String(value);
if (typeof serialized !== 'undefined' && serialized !== '') {
select.where(`${selector} = ?`, [serialized]);
count.where(`${selector} = ?`, [serialized]);
}
});
Object.entries(span).forEach(([key, values]) => {
const info = getColumnInfo(key, model).last;
if (!info)
return;
const q = engine.dialect.q;
const selector = `${q}${info.model.snake}${q}.${q}${info.column.snake}${q}`;
if (typeof values[0] !== 'undefined'
&& values[0] !== null
&& values[0] !== '') {
const value = info.column.unserialize(values[0], undefined, seed);
const serialized = stringable.includes(info.column.type)
? toSqlString(value)
: floatable.includes(info.column.type)
? toSqlFloat(value)
: intable.includes(info.column.type)
? toSqlInteger(value)
: boolable.includes(info.column.type)
? toSqlBoolean(value)
: dateable.includes(info.column.type)
? toSqlDate(value)?.toISOString()
: String(value);
if (typeof serialized !== 'undefined' && serialized !== '') {
select.where(`${selector} >= ?`, [serialized]);
count.where(`${selector} >= ?`, [serialized]);
}
}
if (typeof values[1] !== 'undefined'
&& values[1] !== null
&& values[1] !== '') {
const value = info.column.unserialize(values[1], undefined, seed);
const serialized = stringable.includes(info.column.type)
? toSqlString(value)
: floatable.includes(info.column.type)
? toSqlFloat(value)
: intable.includes(info.column.type)
? toSqlInteger(value)
: boolable.includes(info.column.type)
? toSqlBoolean(value)
: dateable.includes(info.column.type)
? toSqlDate(value)?.toISOString()
: String(value);
if (typeof serialized !== 'undefined') {
select.where(`${selector} <= ?`, [serialized]);
count.where(`${selector} <= ?`, [serialized]);
}
}
});
Object.entries(sort).forEach(([key, value]) => {
const direction = typeof value === 'string' ? value : '';
if (direction.toLowerCase() !== 'asc'
&& direction.toLowerCase() !== 'desc') {
return;
}
const info = getColumnInfo(key, model).last;
if (!info)
return;
const q = engine.dialect.q;
const selector = `${info.model.snake}${q}.${q}${info.column.snake}`;
select.order(selector, direction.toUpperCase());
});
try {
const results = await select;
const total = useTotal ? await count : [{ total: 0 }];
const rows = [];
results.forEach(row => {
const nest = new Nest();
Object.entries(row).forEach(([alias, value]) => {
const selector = info.find(selector => selector.alias === alias);
if (!selector) {
return nest.withPath.set(alias.trim()
.replaceAll('__', '.')
.replace(/([a-z])_([a-z0-9])/ig, (_, a, b) => a + b.toUpperCase()), value);
}
nest.withPath.set(selector.name, selector.last.column.unserialize(value, undefined, seed));
});
rows.push(nest.get());
});
return toResponse(rows, Number(total[0].total) || undefined);
}
catch (e) {
return toErrorResponse(e);
}
}
;