UNPKG

@infect/api

Version:
205 lines (140 loc) 6.3 kB
import Entity from './Entity.js'; import fs from 'fs'; import util from 'util'; import path from 'path'; import SelectionParser from './SelectionParser.js'; import log from 'ee-log'; import types from 'ee-types'; const readFile = util.promisify(fs.readFile); export default class Service { constructor({ schema, name, env, port, }) { this.port = port; this.name = name; this.schema = schema; this.env = env; this.dirname = path.dirname(new URL(import.meta.url).pathname); this.entites = new Map(); } async registerRoutes(router) { for (const entityConfig of this.schema) { // get data from fs const data = await readFile(path.join(this.dirname, `../data/${this.env}/${entityConfig.name}.json`)); const controller = new Entity({ name: entityConfig.name, data: JSON.parse(data), config: entityConfig, serviceName: this.name, port: this.port, }); this.entites.set(entityConfig.name, controller); router.get(`/${this.name}.${entityConfig.name}`, (request) => { this.handleRequest(request, entityConfig); }); router.get(`/${this.name}.${entityConfig.name}/:id`, (request) => { this.handleRequest(request, entityConfig); }); // new api style router.get(`/core-data/v1/${this.name}.${entityConfig.name}`, (request) => { this.handleRequest(request, entityConfig); }); router.get(`/core-data/v1/${this.name}.${entityConfig.name}/:id`, (request) => { this.handleRequest(request, entityConfig); }); } } handleRequest(request, entityConfig) { const selection = new SelectionParser().parse(request); const languages = this.getRequestLanaguages(request); const controller = this.entites.get(entityConfig.name); const options = { languages: languages, selection: selection, id: request.hasParameter('id') ? parseInt(request.getParameter('id'), 10) : null, }; const method = options.id ? 'listOne' : 'list'; controller[method](options).then((data) => { data = this.handleFilter(request.getHeader('filter'), data); request.response().send(data); }).catch((err) => { log(err); if (err.httpCode) request.response().status(err.httpCode); request.response().send(err); }); } /** * filter the data after it was loaded: because * it's simple and fast implemented. */ handleFilter(filterHeader, rows) { if (filterHeader && rows && rows.length) { const filters = filterHeader.split(/\s*,\s*/gi); for (const filter of filters) { const filterParts = /\s*([a-z0-9_\.]+)\s*([=<>])\s*(.*)/gi.exec(filter); if (filterParts) { let value = filterParts[3].trim(); // unescape commas value = value.replace(/;;/g, ','); if (value !== '') { if (!(/[^0-9]/gi.test(value))) value = parseInt(value, 10); else if (!(/[^0-9\.]/gi.test(value))) value = parseFloat(value); else if (value.toLowerCase() === 'null') value = null; else if (value.toLowerCase() === 'not null') value = null; else if (value.toLowerCase() === 'true') value = true; else if (value.toLowerCase() === 'false') value = false; else if (/^["'].*["']$/gi.test(value)) value = value.substr(1, value.length - 2); } // start filtering rows = rows.filter(item => this.satisfiesFilter(item, filterParts[1].split('.'), filterParts[2].trim(), value)); } else throw new Error(`Failed to parse filter '${filter}'!`); } } return rows; } satisfiesFilter(item, filterPath, comparator, value) { if (filterPath.length === 1) { // lets compare values switch (comparator) { case '=': return item[filterPath[0]] == value; case '>': return item[filterPath[0]] > value; case '<': return item[filterPath[0]] < value; default: throw new Error(`Unknown filter comparator '${comparator}'!`); } } else if (filterPath.length > 1) { const localEntity = item[filterPath[0]]; if (types.array(localEntity)) { return localEntity.some(item => this.satisfiesFilter(item, filterPath.slice(1), comparator, value)); } else if (types.object(localEntity)) { return this.satisfiesFilter(localEntity, filterPath.slice(1), comparator, value); } else if (types.undefined(localEntity)) { return false; } else throw new Error(`Cannot follow path into entity ${filterPath[0]}, it is not an entity but typof ${type(localEntity)}!`); } else return false; } getRequestLanaguages(request) { return this.parseRFCPrioritizedHeader(request.getHeader('accept-language')); } parseRFCPrioritizedHeader(headerText) { return headerText .split(/\s*,\s*/gi) .map((item) => { const parsed = /([a-z]{2})-?([a-z]{2})?(?:\s*;\s*q\s*=\s*)?([0-9\.]+)?/gi.exec(item); if (!parsed) return null; return { language: (parsed[1] || '').toLowerCase().trim(), country: (parsed[2] || '').toLowerCase().trim(), priority: parsed[3] ? parseFloat(parsed[3]) : 1, }; }) .filter(item => item !== null) .sort((a, b) => a.priority < b.priority ? 1 : -1); } }